目录
一、需求
(一)投资人投标
(二)流程
二、标的详情
(一)需求
(二)后端
(三)前端
三、计算收益
(一)四种还款方式
(二)后端
(三)前端
四、投标
(一)后端
(二)前端
五、回调接口
一、需求
(一)投资人投标
平台发布标的,出借人充值就可以投资标的,只需满足以下条件
操作表如下:一个标的可以有多个投资人投标,投标数据在表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);
}