尚融宝26-投标

news2024/11/24 6:29:48

目录

一、需求

(一)投资人投标

(二)流程

二、标的详情

(一)需求

(二)后端

(三)前端

三、计算收益

(一)四种还款方式

(二)后端

(三)前端

四、投标

(一)后端

(二)前端

五、回调接口


一、需求

(一)投资人投标

平台发布标的,出借人充值就可以投资标的,只需满足以下条件

操作表如下:一个标的可以有多个投资人投标,投标数据在表lend_item中,后期标的放款后,需要生成借款人还款计划(lend_return表)以及投资人回款计划(lend_item_return表)

汇付宝与尚融宝调用流程图

(二)流程

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

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

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

step4:跳转到汇付宝页面(资金托管接口调用)

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

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

            汇付宝新增投资记录(新增user_invest记录)

step7:异步回调

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

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

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

(4)添加交易流水

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

二、标的详情

(一)需求

展示信息:

1、标的基本信息(标的表 lend)

2、借款人信息(借款人表 borrower)

3、账户余额信息(会员账户表 user_account)

4、根据投资金额计算收益(根据四种还款方式计算)

5、投资记录(投资记录表 lend_item,后续完善)

6、还款记录(还款记录表 lend_return,后续完善)

投标条件:

1、已经登录的会员

2、只有投资人可以投标,借款人不可以投标

3、投标金额必须是100整数倍

4、账号可用余额充足

5、同意投标协议

(二)后端

controller

标的和借款人信息接口LendController

@ApiOperation("获取标的信息")
@GetMapping("/show/{id}")
public R show(
    @ApiParam(value = "标的id", required = true)
    @PathVariable Long id) {
    Map<String, Object> lendDetail = lendService.getLendDetail(id);
    return R.ok().data("lendDetail", lendDetail);
}

账户余额信息接口UserAccountController

@ApiOperation("查询账户余额")
@GetMapping("/auth/getAccount")
public R getAccount(HttpServletRequest request){
    String token = request.getHeader("token");
    Long userId = JwtUtils.getUserId(token);
    BigDecimal account = userAccountService.getAccount(userId);
    return R.ok().data("account", account);
}

service

接口:UserAccountService

BigDecimal getAccount(Long userId);

实现:UserAccountServiceImpl

@Override
public BigDecimal getAccount(Long userId) {
    //根据userId查找用户账户
    QueryWrapper<UserAccount> userAccountQueryWrapper = new QueryWrapper<>();
    userAccountQueryWrapper.eq("user_id", userId);
    UserAccount userAccount = baseMapper.selectOne(userAccountQueryWrapper);

    BigDecimal amount = userAccount.getAmount();
    return amount;
}

 

(三)前端

pages/lend/_id.vue

获取标的详情信息

  async asyncData({ $axios, params }) {
    let lendId = params.id //通过路由参数获取标的id

    //通过lendId获取标的详情信息
    let response = await $axios.$get('/api/core/lend/show/' + lendId)

    return {
      lend: response.data.lendDetail.lend, //标的详情
      borrower: response.data.lendDetail.borrower, //借款人信息
    }
  },

查询账户余额

    //查询账户余额
    fetchAccount() {
      let userInfo = cookie.get('userInfo')
      if (userInfo) {
        this.$axios
          .$get('/api/core/userAccount/auth/getAccount')
          .then((response) => {
            this.account = response.data.account
          })
      }
    },

获取登陆人的用户类型

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

三、计算收益

(一)四种还款方式

1、等额本息

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

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

计算公式为:

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

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

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

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

2、等额本金

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

计算公式为:

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

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

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

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

3、按期付息到期还本

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

计算公式为:

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

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

4、一次还本付息

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

计算公式为:

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

(二)后端

1、四种还款方式计算的工具类

根据我们的表设计,出借人要能知道每月回款的本金与利息,借款人也一样,他也要知道每月的还款本金与利息,还有我们需要计算投资人的投资收益等数据。

因此我们将四种还款方式工具类设计如下:

 Amount1Helper

/**
 * 等额本息工具类
 * 校验网址:http://www.xjumc.com/
 * 等额本息是指一种贷款的还款方式,是在还款期内,
 * 每月偿还同等数额的贷款(包括本金和利息),
 * 和等额本金是不一样的概念,虽然刚开始还款时
 * 每月还款额可能会低于等额本金还款方式,
 * 但是最终所还利息会高于等额本金还款方式,
 * 该方式经常被银行使用。
 *
 * 每月还款数额计算公式如下:
 * 每月还款额=贷款本金×[月利率×(1+月利率) ^ 还款月数]÷{[(1+月利率) ^ 还款月数]-1}
 */
public class Amount1Helper {

    /**
     * 每月还款利息
     * @param invest 总借款额(贷款本金)
     * @param yearRate 年利率
     * @param totalMonth 还款总月数
     * @return 每月偿还利息
     */
    public static Map<Integer, BigDecimal> getPerMonthInterest(BigDecimal invest, BigDecimal yearRate, int totalMonth) {
        Map<Integer, BigDecimal> map = new HashMap();
        //月利息
        double monthRate = yearRate.divide(new BigDecimal("12"), 8, BigDecimal.ROUND_DOWN).doubleValue();
        BigDecimal monthInterest;
        for (int i = 1; i < totalMonth + 1; i++) {
            BigDecimal multiply = invest.multiply(new BigDecimal(monthRate));
            BigDecimal sub  = new BigDecimal(Math.pow(1 + monthRate, totalMonth)).subtract(new BigDecimal(Math.pow(1 + monthRate, i-1)));
            monthInterest = multiply.multiply(sub).divide(new BigDecimal(Math.pow(1 + monthRate, totalMonth) - 1), 8, BigDecimal.ROUND_DOWN);
            monthInterest = monthInterest.setScale(2, BigDecimal.ROUND_DOWN);
            map.put(i, monthInterest);
        }
        return map;
    }

    /**
     * 每月还款本金
     * @param invest 总借款额(贷款本金)
     * @param yearRate 年利率
     * @param totalMonth 还款总月数
     * @return 每月偿还本金
     */
    public static Map<Integer, BigDecimal> getPerMonthPrincipal(BigDecimal invest, BigDecimal yearRate, int totalMonth) {
        double monthRate = yearRate.divide(new BigDecimal("12"), 8, BigDecimal.ROUND_DOWN).doubleValue();
        BigDecimal monthIncome = invest.multiply(new BigDecimal(monthRate * Math.pow(1 + monthRate, totalMonth)))
                .divide(new BigDecimal(Math.pow(1 + monthRate, totalMonth) - 1), 8, BigDecimal.ROUND_DOWN);
        Map<Integer, BigDecimal> mapInterest = getPerMonthInterest(invest, yearRate, totalMonth);
        Map<Integer, BigDecimal> mapPrincipal = new HashMap();

        for (Map.Entry<Integer, BigDecimal> entry : mapInterest.entrySet()) {
            mapPrincipal.put(entry.getKey(), monthIncome.subtract(entry.getValue()));
        }
        return mapPrincipal;
    }

    /**
     * 总利息
     * @param invest 总借款额(贷款本金)
     * @param yearRate 年利率
     * @param totalMonth 还款总月数
     * @return 总利息
     */
    public static BigDecimal getInterestCount(BigDecimal invest, BigDecimal yearRate, int totalMonth) {
        BigDecimal count = new BigDecimal(0);
        Map<Integer, BigDecimal> mapInterest = getPerMonthInterest(invest, yearRate, totalMonth);

        for (Map.Entry<Integer, BigDecimal> entry : mapInterest.entrySet()) {
            count = count.add(entry.getValue());
        }
        return count;
    }

    public static void main(String[] args) {
        BigDecimal invest = new BigDecimal("500"); // 本金
        int month = 12;
        BigDecimal yearRate = new BigDecimal("0.20"); // 年利率
        Map mapInterest = getPerMonthInterest(invest, yearRate, month);
        System.out.println("等额本息---每月还款利息:" + mapInterest);
        Map mapPrincipal = getPerMonthPrincipal(invest, yearRate, month);
        System.out.println("等额本息---每月还款本金:" + mapPrincipal);
        BigDecimal count = getInterestCount(invest, yearRate, month);
        System.out.println("等额本息---总利息:" + count);
    }
}

 

Amount2Helper
/**
 * 等额本金工具类
 * 校验网址:http://www.xjumc.com/
 * 等额本金是指一种贷款的还款方式,是在还款期内把贷款数总额等分,每月偿还同等数额的本金和剩余贷款在该月所产生的利息,这样由于每月的还款本金额固定,
 *  * 而利息越来越少,借款人起初还款压力较大,但是随时间的推移每月还款数也越来越少。
 */
public class Amount2Helper {

    /**
     * 每月本息
     * @param invest 总借款额(贷款本金)
     * @param yearRate 年利率
     * @param totalMonth 还款总月数
     * @return 每月偿还利息
     */
    public static Map<Integer, BigDecimal> getPerMonthPrincipalInterest(BigDecimal invest, BigDecimal yearRate, int totalMonth) {
        Map<Integer, BigDecimal> map = new HashMap();
        // 每月本金
        BigDecimal monthPri = invest.divide(new BigDecimal(totalMonth), 8, BigDecimal.ROUND_DOWN);
        // 获取月利率
        double monthRate = yearRate.divide(new BigDecimal(12), 8, BigDecimal.ROUND_DOWN).doubleValue();
        monthRate = new BigDecimal(monthRate).setScale(8, BigDecimal.ROUND_DOWN).doubleValue();
        for (int i = 1; i <= totalMonth; i++) {
            double monthRes = monthPri.doubleValue() + (invest.doubleValue() - monthPri.doubleValue() * (i - 1)) * monthRate;
            monthRes = new BigDecimal(monthRes).setScale(2, BigDecimal.ROUND_DOWN).doubleValue();
            map.put(i, new BigDecimal(monthRes));
        }
        return map;
    }

    /**
     * 每月还款利息
     * @param invest 总借款额(贷款本金)
     * @param yearRate 年利率
     * @param totalMonth 还款总月数
     * @return 每月偿还本金
     * @return
     */
    public static Map<Integer, BigDecimal> getPerMonthInterest(BigDecimal invest, BigDecimal yearRate, int totalMonth) {
        Map<Integer, BigDecimal> inMap = new HashMap();
        BigDecimal principal = invest.divide(new BigDecimal(totalMonth), 8, BigDecimal.ROUND_DOWN);
        Map<Integer, BigDecimal> map = getPerMonthPrincipalInterest(invest, yearRate, totalMonth);
        for (Map.Entry<Integer, BigDecimal> entry : map.entrySet()) {
            BigDecimal principalBigDecimal = principal;
            BigDecimal principalInterestBigDecimal = new BigDecimal(entry.getValue().toString());
            BigDecimal interestBigDecimal = principalInterestBigDecimal.subtract(principalBigDecimal);
            interestBigDecimal = interestBigDecimal.setScale(2, BigDecimal.ROUND_DOWN);
            inMap.put(entry.getKey(), interestBigDecimal);
        }
        return inMap;
    }

    /**
     * 每月还款本金
     * @param invest 总借款额(贷款本金)
     * @param yearRate 年利率
     * @param totalMonth 还款总月数
     * @return 总利息
     */
    public static Map<Integer, BigDecimal> getPerMonthPrincipal(BigDecimal invest, BigDecimal yearRate, int totalMonth) {
        Map<Integer, BigDecimal> map = new HashMap<>();
        BigDecimal monthIncome = invest.divide(new BigDecimal(totalMonth), 8, BigDecimal.ROUND_DOWN);
        for(int i=1; i<=totalMonth; i++) {
            map.put(i, monthIncome);
        }
        return map;
    }

    /**
     * 总利息
     * @param invest
     * @param yearRate
     * @param totalMonth
     * @return
     */
    public static BigDecimal getInterestCount(BigDecimal invest, BigDecimal yearRate, int totalMonth) {
        BigDecimal count = new BigDecimal(0);
        Map<Integer, BigDecimal> mapInterest = getPerMonthInterest(invest, yearRate, totalMonth);

        for (Map.Entry<Integer, BigDecimal> entry : mapInterest.entrySet()) {
            count = count.add(entry.getValue());
        }
        return count;
    }

    public static void main(String[] args) {
        BigDecimal invest = new BigDecimal("12000"); // 本金
        int month = 12;
        BigDecimal yearRate = new BigDecimal("0.12"); // 年利率

        Map benjin = getPerMonthPrincipal(invest, yearRate, month);
        System.out.println("等额本金---每月本金:" + benjin);
        Map mapInterest = getPerMonthInterest(invest, yearRate, month);
        System.out.println("等额本金---每月利息:" + mapInterest);
        BigDecimal count = getInterestCount(invest, yearRate, month);
        System.out.println("等额本金---总利息:" + count);
    }
}
Amount3Helper
/**
 * 按月付息到期还本工具类
 */
public class Amount3Helper {

    /**
     * 每月还款利息
     * 按月付息,到期还本-计算获取还款方式为按月付息,到期还本的每月偿还利息
     * 公式:每月应还利息=总借款额*年利率÷还款月数
     *
     * @param invest 总借款额(贷款本金)
     * @param yearRate 年利率
     * @param totalMonth 还款总月数
     * @return 每月偿还利息
     */
    public static Map getPerMonthInterest(BigDecimal invest, BigDecimal yearRate, int totalMonth) {
        Map<Integer, BigDecimal> map = new HashMap<>();
        //每月偿还利息
        BigDecimal monthIncome = invest.multiply(yearRate).divide(new BigDecimal(12), 8, BigDecimal.ROUND_DOWN);
        for(int i=1; i<=totalMonth; i++) {
            map.put(i, monthIncome);
        }
        return map;
    }

    /**
     * 每月偿还本金
     * @param invest 总借款额(贷款本金)
     * @param yearRate 年利率
     * @param totalMonth 还款总月数
     * @return 每月偿还本金
     */
    public static Map<Integer, BigDecimal> getPerMonthPrincipal(BigDecimal invest, BigDecimal yearRate, int totalMonth) {
        Map<Integer, BigDecimal> map = new HashMap<Integer, BigDecimal>();
        // 每月利息
        for (int i = 1; i <= totalMonth; i++) {
            if(i == totalMonth){
                map.put(i, invest);
            } else {
                map.put(i, new BigDecimal("0"));
            }
        }
        return map;
    }

    /**
     * 总利息
     * @param invest 总借款额(贷款本金)
     * @param yearRate 年利率
     * @param totalMonth 还款总月数
     * @return 总利息
     */
    public static BigDecimal getInterestCount(BigDecimal invest, BigDecimal yearRate, int totalMonth) {
        //每月偿还利息
        BigDecimal count = invest.multiply(yearRate).divide(new BigDecimal(12), 2, BigDecimal.ROUND_DOWN);

        return count.multiply(new BigDecimal(totalMonth));
    }

    public static void main(String[] args) {
        BigDecimal invest = new BigDecimal("10000"); // 本金
        int month = 12;
        BigDecimal yearRate = new BigDecimal("0.12"); // 年利率

        Map getPerMonthPrincipalInterest = getPerMonthPrincipal(invest, yearRate, month);
        System.out.println("按月付息到期还本---每月偿还本金:" + getPerMonthPrincipalInterest);
        Map mapInterest = getPerMonthInterest(invest, yearRate, month);
        System.out.println("按月付息到期还本---每月偿还利息:" + mapInterest);
        BigDecimal count = getInterestCount(invest, yearRate, month);
        System.out.println("按月付息到期还本---总利息:" + count);
    }
}

 

Amount4Helper
/**
 * 一次还本还息工具类
 */
public class Amount4Helper {

    /**
     * 还款金额 = 本金 + 本金*月利率*期限
     * @param amount
     * @param yearRate
     * @param totalmonth
     * @return
     */
    public static Map<Integer, BigDecimal> getPerMonthInterest(BigDecimal amount, BigDecimal yearRate, int totalmonth) {
        Map<Integer, BigDecimal> map = new HashMap<>();
        BigDecimal monthInterest = yearRate.divide(new BigDecimal("12"), 8, BigDecimal.ROUND_HALF_UP);
        BigDecimal multiply = amount.multiply(monthInterest).multiply(new BigDecimal(totalmonth));
        map.put(1, multiply);
        return map;
    }

    /**
     * 还款本金
     * @param amount
     * @param yearRate
     * @param totalmonth
     * @return
     */
    public static Map<Integer, BigDecimal> getPerMonthPrincipal(BigDecimal amount, BigDecimal yearRate, int totalmonth) {
        Map<Integer, BigDecimal> map = new HashMap<>();
        map.put(1, amount);
        return map;
    }

    /**
     * 总利息
     * @param amount
     * @param yearRate
     * @param totalmonth
     * @return
     */
    public static BigDecimal getInterestCount(BigDecimal amount, BigDecimal yearRate, int totalmonth) {
        BigDecimal monthInterest = yearRate.divide(new BigDecimal("12"), 8, BigDecimal.ROUND_HALF_UP);
        BigDecimal multiply =amount.multiply(monthInterest).multiply(new BigDecimal(totalmonth)).divide(new BigDecimal("1"), 8, BigDecimal.ROUND_HALF_UP);
        return multiply;
    }


    public static void main(String[] args) {
        BigDecimal invest = new BigDecimal("10000"); // 本金
        int month = 12;
        BigDecimal yearRate = new BigDecimal("0.12"); // 年利率

        Map getPerMonthPrincipalInterest = getPerMonthPrincipal(invest, yearRate, month);
        System.out.println("一次还本还息---偿还本金:" + getPerMonthPrincipalInterest);
        Map mapInterest = getPerMonthInterest(invest, yearRate, month);
        System.out.println("一次还本还息---总利息:" + mapInterest);
        BigDecimal count = getInterestCount(invest, yearRate, month);
        System.out.println("一次还本还息---总利息:" + count);
    }
}

 

2、枚举类

ReturnMethodEnum

@AllArgsConstructor
@Getter
public enum ReturnMethodEnum {

    ONE(1, "等额本息"),
    TWO(2, "等额本金"),
    THREE(3, "每月还息一次还本"),
    FOUR(4, "一次还本还息"),
    ;

    private Integer method;
    private String msg;
}

3、controller

LendController

@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().data("interestCount", interestCount);
}

4、service

接口:LendService

BigDecimal getInterestCount(BigDecimal invest, BigDecimal yearRate, Integer totalmonth, Integer returnMethod);

实现:LendServiceImpl

@Override
public BigDecimal getInterestCount(BigDecimal invest, BigDecimal yearRate, Integer totalmonth, Integer returnMethod) {

    BigDecimal interestCount;
    //计算总利息
    if (returnMethod.intValue() == ReturnMethodEnum.ONE.getMethod()) {
        interestCount = Amount1Helper.getInterestCount(invest, yearRate, totalmonth);
    } else if (returnMethod.intValue() == ReturnMethodEnum.TWO.getMethod()) {
        interestCount = Amount2Helper.getInterestCount(invest, yearRate, totalmonth);
    } else if(returnMethod.intValue() == ReturnMethodEnum.THREE.getMethod()) {
        interestCount = Amount3Helper.getInterestCount(invest, yearRate, totalmonth);
    } else {
        interestCount = Amount4Helper.getInterestCount(invest, yearRate, totalmonth);
    }
    return interestCount;
}

 

(三)前端

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
    })
},

四、投标

(一)后端

1、实现思路

投标时要在服务器端校验数据:

  • 标的状态必须为募资中
  • 标的不能超卖
  • 账户可用余额充足

2、创建VO

@Data
@ApiModel(description = "投标信息")
public class InvestVO {

    private Long lendId;

    //投标金额
    private String investAmount;

    //用户id
    private Long investUserId;

    //用户姓名
    private String investName;
}

3、controller

@Api(tags = "标的的投资")
@RestController
@RequestMapping("/api/core/lendItem")
@Slf4j
public class LendItemController {
    
    @Resource
    LendItemService lendItemService;

    @ApiOperation("会员投资提交数据")
    @PostMapping("/auth/commitInvest")
    public R commitInvest(@RequestBody InvestVO investVO, HttpServletRequest request) {

        String token = request.getHeader("token");
        Long userId = JwtUtils.getUserId(token);
        String userName = JwtUtils.getUserName(token);
        investVO.setInvestUserId(userId);
        investVO.setInvestName(userName);

        //构建充值自动提交表单
        String formStr = lendItemService.commitInvest(investVO);
        return R.ok().data("formStr", formStr);
    }
}

4、service

接口:LendItemService

String commitInvest(InvestVO investVO);

实现:LendItemServiceImpl

@Resource
private LendMapper lendMapper;

@Resource
private LendService lendService;

@Resource
private UserAccountService userAccountService;

@Resource
private UserBindService userBindService;


@Override
public String commitInvest(InvestVO investVO) {

    //输入校验==========================================
    Long lendId = investVO.getLendId();
    //获取标的信息
    Lend lend = lendMapper.selectById(lendId);

    //标的状态必须为募资中
    Assert.isTrue(
        lend.getStatus().intValue() == LendStatusEnum.INVEST_RUN.getStatus().intValue(),
        ResponseEnum.LEND_INVEST_ERROR);

    //标的不能超卖:(已投金额 + 本次投资金额 )>=标的金额(超卖)
    BigDecimal sum = lend.getInvestAmount().add(new BigDecimal(investVO.getInvestAmount()));
    Assert.isTrue(sum.doubleValue() <= lend.getAmount().doubleValue(),
                  ResponseEnum.LEND_FULL_SCALE_ERROR);

    //账户可用余额充足:当前用户的余额 >= 当前用户的投资金额(可以投资)
    Long investUserId = investVO.getInvestUserId();
    BigDecimal amount = userAccountService.getAccount(investUserId);//获取当前用户的账户余额
    Assert.isTrue(amount.doubleValue() >= Double.parseDouble(investVO.getInvestAmount()),
                  ResponseEnum.NOT_SUFFICIENT_FUNDS_ERROR);

    //在商户平台中生成投资信息==========================================
    //标的下的投资信息
    LendItem lendItem = new LendItem();
    lendItem.setInvestUserId(investUserId);//投资人id
    lendItem.setInvestName(investVO.getInvestName());//投资人名字
    String lendItemNo = LendNoUtils.getLendItemNo();
    lendItem.setLendItemNo(lendItemNo); //投资条目编号(一个Lend对应一个或多个LendItem)
    lendItem.setLendId(investVO.getLendId());//对应的标的id
    lendItem.setInvestAmount(new BigDecimal(investVO.getInvestAmount())); //此笔投资金额
    lendItem.setLendYearRate(lend.getLendYearRate());//年化
    lendItem.setInvestTime(LocalDateTime.now()); //投资时间
    lendItem.setLendStartDate(lend.getLendStartDate()); //开始时间
    lendItem.setLendEndDate(lend.getLendEndDate()); //结束时间

    //预期收益
    BigDecimal expectAmount = lendService.getInterestCount(
        lendItem.getInvestAmount(),
        lendItem.getLendYearRate(),
        lend.getPeriod(),
        lend.getReturnMethod());
    lendItem.setExpectAmount(expectAmount);

    //实际收益
    lendItem.setRealAmount(new BigDecimal(0));

    lendItem.setStatus(0);//默认状态:刚刚创建
    baseMapper.insert(lendItem);


    //组装投资相关的参数,提交到汇付宝资金托管平台==========================================
    //在托管平台同步用户的投资信息,修改用户的账户资金信息==========================================
    //获取投资人的绑定协议号
    String bindCode = userBindService.getBindCodeByUserId(investUserId);
    //获取借款人的绑定协议号
    String benefitBindCode = userBindService.getBindCodeByUserId(lend.getUserId());

    //封装提交至汇付宝的参数
    Map<String, Object> paramMap = new HashMap<>();
    paramMap.put("agentId", HfbConst.AGENT_ID);
    paramMap.put("voteBindCode", bindCode);
    paramMap.put("benefitBindCode",benefitBindCode);
    paramMap.put("agentProjectCode", lend.getLendNo());//项目标号
    paramMap.put("agentProjectName", lend.getTitle());

    //在资金托管平台上的投资订单的唯一编号,要和lendItemNo保持一致。
    paramMap.put("agentBillNo", lendItemNo);//订单编号
    paramMap.put("voteAmt", investVO.getInvestAmount());
    paramMap.put("votePrizeAmt", "0");
    paramMap.put("voteFeeAmt", "0");
    paramMap.put("projectAmt", lend.getAmount()); //标的总金额
    paramMap.put("note", "");
    paramMap.put("notifyUrl", HfbConst.INVEST_NOTIFY_URL); //检查常量是否正确
    paramMap.put("returnUrl", HfbConst.INVEST_RETURN_URL);
    paramMap.put("timestamp", RequestHelper.getTimestamp());
    String sign = RequestHelper.getSign(paramMap);
    paramMap.put("sign", sign);

    //构建充值自动提交表单
    String formStr = FormHelper.buildForm(HfbConst.INVEST_URL, paramMap);
    return formStr;
}

 

5、userBindService

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

接口:UserBindService

String getBindCodeByUserId(Long userId);

实现:UserBindServiceImpl

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

 

(二)前端

pages/lend/_id.vue

    //投资
    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.account) {
        this.$message.error('余额不足,请充值')
        return
      }

      //数据提交
      this.$alert(
        '<div style="size: 18px;color: red;">您即将前往汇付宝确认标的</div>',
        '前往汇付宝资金托管平台',
        {
          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/commitInvest', this.invest)
                .then((response) => {
                  // console.log(response.data.formStr)
                  // debugger
                  document.write(response.data.formStr)
                })
            }
          },
        }
      )
    }

五、回调接口

LendItemController中创建回调方法 

@ApiOperation("会员投资异步回调")
@PostMapping("/notify")
public String notify(HttpServletRequest request) {

    Map<String, Object> paramMap = RequestHelper.switchMap(request.getParameterMap());
    log.info("用户投资异步回调:" + JSON.toJSONString(paramMap));

    //校验签名 P2pInvestNotifyVo
    if(RequestHelper.isSignEquals(paramMap)) {
        if("0001".equals(paramMap.get("resultCode"))) {
            lendItemService.notify(paramMap);
        } else {
            log.info("用户投资异步回调失败:" + JSON.toJSONString(paramMap));
            return "fail";
        }
    } else {
        log.info("用户投资异步回调签名错误:" + JSON.toJSONString(paramMap));
        return "fail";
    }
    return "success";
}

实现回调的业务

接口:LendItemService

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

实现:LendItemServiceImpl

@Resource
private TransFlowService transFlowService;

@Resource
private UserAccountMapper userAccountMapper;

@Transactional(rollbackFor = Exception.class)
@Override
public void notify(Map<String, Object> paramMap) {

    log.info("投标成功");

    //获取投资编号
    String agentBillNo = (String)paramMap.get("agentBillNo");

    boolean result = transFlowService.isSaveTransFlow(agentBillNo);
    if(result){
        log.warn("幂等性返回");
        return;
    }

    //获取用户的绑定协议号
    String bindCode = (String)paramMap.get("voteBindCode");
    String voteAmt = (String)paramMap.get("voteAmt");

    //修改商户系统中的用户账户金额:余额、冻结金额
    userAccountMapper.updateAccount(bindCode, new BigDecimal("-" + voteAmt), new BigDecimal(voteAmt));

    //修改投资记录的投资状态改为已支付
    LendItem lendItem = this.getByLendItemNo(agentBillNo);
    lendItem.setStatus(1);//已支付
    baseMapper.updateById(lendItem);

    //修改标的信息:投资人数、已投金额
    Long lendId = lendItem.getLendId();
    Lend lend = lendMapper.selectById(lendId);
    lend.setInvestNum(lend.getInvestNum() + 1);
    lend.setInvestAmount(lend.getInvestAmount().add(lendItem.getInvestAmount()));
    lendMapper.updateById(lend);

    //新增交易流水
    TransFlowBO transFlowBO = new TransFlowBO(
        agentBillNo,
        bindCode,
        new BigDecimal(voteAmt),
        TransTypeEnum.INVEST_LOCK,
        "投资项目编号:" + lend.getLendNo() + ",项目名称:" + lend.getTitle());
    transFlowService.saveTransFlow(transFlowBO);
}

LendItemServiceImpl

private LendItem getByLendItemNo(String lendItemNo) {
    QueryWrapper<LendItem> queryWrapper = new QueryWrapper();
    queryWrapper.eq("lend_item_no", lendItemNo);
    return baseMapper.selectOne(queryWrapper);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/477210.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于 A* 搜索算法来优化无线传感器节点网络的平均电池寿命(Matlab代码实现)

目录 &#x1f4a5;1 概述 &#x1f4da;2 运行结果 &#x1f389;3 参考文献 &#x1f468;‍&#x1f4bb;4 Matlab代码 &#x1f4a5;1 概述 A*&#xff08;念做&#xff1a;A Star&#xff09;算法是一种很常用的路径查找和图形遍历算法。它有较好的性能和准确度。本文…

一篇带你快速入门DDD领域驱动设计

一、什么是领域驱动 领域驱动设计 Domain-Driven Design&#xff0c;简称DDD。软件对于行业并没有这么高的要求&#xff0c;他本身就是帮助其他行业更好的发展&#xff0c;赋能其他行业的。各个行业都有软件的身影&#xff0c;但是他们的业务场景是不同的&#xff0c;所以就需…

【MYSQL】数据类型和约束

目录 数据类型 1.数值类型 1.1.位--类型bit(M) 1.2. 整数类型--tinyint&#xff0c;smallint&#xff0c;int&#xff0c;bigint 1.3.小数类型--float、decimal 2.字符类型--char、varchar 3.日期类型--datetime、timestamp 4.string类型--enum和set mysql的约束 1.空…

Mybatis 知识总结2(基于注解的增删改查操作)

3.3 MyBatis 增删改查&#xff08;注解方式&#xff09; MyBatis 的增删改查是最基础最核心的功能&#xff0c;需要重点掌握。 需求说明 对员工信息进行增删改查操作。 查询&#xff08;查询结果分页展示后续实现&#xff09; 根据主键ID查询根据条件查询 新增更新删除 根据主…

海思芯片(hi3536av100)启动模式选择

1、支持多种模式可配置 (1)支持BootRom启动 (2)支持从 SPI NOR Flash 启动 (3)支持从 SPI NAND Flash 启动 (4)支持从 eMMC 启动 (5)支持 PCIe 从片启动 2、启动方式的选择 BOOT_SEL[1:0]SFC_EMMC_BOOT_MODEMODE000SPI Nor Flash 3 Byte001SPI Nor Flash 4 Byte010SPI Nand Fl…

2005-2020全国及各省家庭承包耕地流转总面积及经营耕地面积

2005-2020全国及各省家庭承包耕地流转总面积及经营耕地面积 1、时间&#xff1a;时间&#xff1a;2005-2020年 2、范围&#xff1a;包括全国及30个省份不包括西藏 3、指标包括&#xff1a;家庭承包耕地流转总面积(亩)、家庭承包经营耕地面积(亩) 4、来源&#xff1a;农村经…

荔枝派Zero(全志V3S) tftp下载 kernel 和 nfs 挂载文件系统

文章目录 前言一、U-Boot 适配 Ethernet1、配置 U-Boot2、修改 dts 文件3、编译4、烧写到 SD 卡5、测试<1>、查看启动打印信息<2>、ping 测试 二、Kernel 适配 Ethernet1、配置 kernel2、修改 dts 文件3、编译4、拷贝到 SD 卡5、测试<1>、启动网络接口&#…

【嵌入式环境下linux内核及驱动学习笔记-(7-内核 I/O)-多路复用】

目录 2、多路复用2.1 函数select相关2.1.1 应用层select()2.1.2 FD_ZERO2.1.3 FD_SET2.1.4 FD_ISSET 2.2 函数poll相关2.2.1 poll函数 2.3 驱动层 函数2.4 实例 接上篇&#xff0c;继续内核 I/O的五种模式的解读。 2、多路复用 select&#xff0c;poll&#xff0c;epoll都是IO…

常见的接口优化技巧思路

一、背景 针对老项目&#xff0c;去年做了许多降本增效的事情&#xff0c;其中发现最多的就是接口耗时过长的问题&#xff0c;就集中搞了一次接口性能优化。本文将给小伙伴们分享一下接口优化的通用方案。 二、接口优化方案总结 1.批处理 批量思想&#xff1a;批量操作数据…

windows如何确认服务器上程序端口是否正常

方式1&#xff1a;ping命令 ping命令说明 ping命令是个使用频率极高的网络诊断工具&#xff0c;在Windows、Unix和Linux系统下均适用。它是TCP/IP协议的一部分&#xff0c;用于确定本地主机是否能与另一台主机交换数据报。根据返回的信息&#xff0c;我们可以推断TCP/IP参数设…

类和对象 -上(C++)

目录 认识面向过程和面向对象 类的引入 类的定义 语法&#xff1a; 类的两种定义方式&#xff1a; 成员变量命名规则建议 类的访问限定符及封装 访问限定符 C 中 class 和 struct 的区别&#xff1f; 封装 类的作用域 类的实例化 类对象模型 如何计算类对象的大小 结构体的内存…

想保护你的网站?用Python来生成验证码图片

前言 随着互联网的发展&#xff0c;我们越来越多地依赖于网站和应用程序&#xff0c;而这些网站和应用程序也面临着各种各样的安全威胁&#xff0c;其中之一就是用户可能会通过脚本攻击你的网站。为了缓解这些安全风险&#xff0c;一个常见的做法是在用户进行操作时&#xff0…

关于电信设备进网许可制度若干改革举措的通告

Q&#xff1a;3月1日后&#xff0c;不再实行进网许可管理的11种电信设备是否还需要继续申请和使用标志&#xff1f; A&#xff1a;3月1日起&#xff0c;对不再实行进网许可管理的11种电信设备停止核发进网许可标志&#xff0c;已申请的标志可在证书有效期内继续使用。 Q&#…

应用启动时aerospike客户端查询rt高原因

在应用刚起步时&#xff0c;发到预发测试或者生产小部分流量进来时&#xff0c;发现aerospike的rt特别高&#xff0c;在流量稍微大点时&#xff0c;rt恢复正常。基本可以断定客户端存在预热问题。 应用没有设置连接池配置&#xff0c;因此check下默认配置 可以看到&#xff0…

c++类 笔记(陆续更新该文档)

派生类 #include <iostream> using namespace std; class Box{private://类私有&#xff0c;只有成员可以调用 也就是说你不可以通过box1.a来调用 ,这些变量其实你默认不用写private 这个变量&#xff0c;只要放在最上面他默认就是 私有int a1;protected://protected&am…

AlgoC++:课程总结

目录 课程总结前言1. 未讲解内容2. 复习2.1 矩阵求导2.2 优化方法2.3 具体的算法 3. 未来怎么学C(必看&#xff01;&#xff01;&#xff01;) 课程总结 前言 手写AI推出的全新面向AI算法的C课程 Algo C&#xff0c;链接。记录下个人学习笔记&#xff0c;仅供自己参考。 本次课…

【嵌入式环境下linux内核及驱动学习笔记-(8-内核 I/O)-信号驱动】

目录 3 信号驱动的异步通知3.1 linux异步通知编程3.1.1 什么是信号3.1.2 信号的工作流程: 3.2. 应用层3.2.1 信号接收 signal函数3.2.2 应用层 fcntl 函数3.2.3 应用层信号驱动机制步骤 3.3 驱动层3.3.1 驱动层模板3.3.2 驱动层 实现fasync函数3.3.3 fasync_helper3.3.4 struct…

Golang-常见数据结构Slice

Slice slice 翻译成中文就是切片&#xff0c;它和数组&#xff08;array&#xff09;很类似&#xff0c;可以用下标的方式进行访问&#xff0c;如果越界&#xff0c;就会产生 panic。但是它比数组更灵活&#xff0c;可以自动地进行扩容。 了解 slice 的本质, 最简单的方法就是…

MySQL 一条SQL语句是如何执行的?

总览 ​ 所以今天我们把MySQL拆解一下&#xff0c;看看里边有哪些零件。下边是MySQL的基本架构示意图。 大体来说&#xff0c;MySQL分为Server层和存储引擎两部分。 Server 层包括连接器、查询缓存、分析器、优化器、执行器等&#xff0c;涵盖 MySQL 的大多数核心服务功能&am…

小白也能懂的可转债配债价格计算

可转债配债如何计算 先给理论公式&#xff1a; 配债10张/1手所需的钱数 配债所需股数 * 当前的股价 这个公式应该很好理解&#xff0c;不需要做过多的解释。 那&#xff0c; 为什么如此简单的公式&#xff0c;还是很多人不会算&#xff0c;是因为&#xff1a; 配债所需的股数跟…