目录
一、提现
(一)需求
(二)前端
(三)后端
1、提现接口
2、回调接口
二、还款
(一)需求
(二)前端
(三)后端
1、还款接口
2、回调接口
一、提现
(一)需求
放款成功后,借款人可以申请提现。
参考《汇付宝商户账户技术文档》3.15用户申请提现
(二)前端
pages/user/withdraw.vue
<script>
export default {
data() {
return {
fetchAmt: 0,
}
},
methods: {
commitWithdraw() {
this.$alert(
'<div style="size: 18px;color: red;">您即将前往汇付宝提现</div>',
'前往汇付宝资金托管平台',
{
dangerouslyUseHTMLString: true,
confirmButtonText: '立即前往',
callback: (action) => {
if (action === 'confirm') {
this.$axios
.$post(
'/api/core/userAccount/auth/commitWithdraw/' + this.fetchAmt
)
.then((response) => {
document.write(response.data.formStr)
})
}
},
}
)
},
},
}
</script>
(三)后端
1、提现接口
UserAccountController
@ApiOperation("用户提现")
@PostMapping("/auth/commitWithdraw/{fetchAmt}")
public R commitWithdraw(
@ApiParam(value = "金额", required = true)
@PathVariable BigDecimal fetchAmt, HttpServletRequest request) {
String token = request.getHeader("token");
Long userId = JwtUtils.getUserId(token);
String formStr = userAccountService.commitWithdraw(fetchAmt, userId);
return R.ok().data("formStr", formStr);
}
接口:UserAccountService
String commitWithdraw(BigDecimal fetchAmt, Long userId);
实现:UserAccountServiceImpl
@Resource
private UserBindService userBindService;
@Resource
private UserAccountService userAccountService;
@Override
public String commitWithdraw(BigDecimal fetchAmt, Long userId) {
//账户可用余额充足:当前用户的余额 >= 当前用户的提现金额
BigDecimal amount = userAccountService.getAccount(userId);//获取当前用户的账户余额
Assert.isTrue(amount.doubleValue() >= fetchAmt.doubleValue(),
ResponseEnum.NOT_SUFFICIENT_FUNDS_ERROR);
String bindCode = userBindService.getBindCodeByUserId(userId);
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("agentId", HfbConst.AGENT_ID);
paramMap.put("agentBillNo", LendNoUtils.getWithdrawNo());
paramMap.put("bindCode", bindCode);
paramMap.put("fetchAmt", fetchAmt);
paramMap.put("feeAmt", new BigDecimal(0));
paramMap.put("notifyUrl", HfbConst.WITHDRAW_NOTIFY_URL);
paramMap.put("returnUrl", HfbConst.WITHDRAW_RETURN_URL);
paramMap.put("timestamp", RequestHelper.getTimestamp());
String sign = RequestHelper.getSign(paramMap);
paramMap.put("sign", sign);
//构建自动提交表单
String formStr = FormHelper.buildForm(HfbConst.WITHDRAW_URL, paramMap);
return formStr;
}
2、回调接口
UserAccountController
@ApiOperation("用户提现异步回调")
@PostMapping("/notifyWithdraw")
public String notifyWithdraw(HttpServletRequest request) {
Map<String, Object> paramMap = RequestHelper.switchMap(request.getParameterMap());
log.info("提现异步回调:" + JSON.toJSONString(paramMap));
//校验签名
if(RequestHelper.isSignEquals(paramMap)) {
//提现成功交易
if("0001".equals(paramMap.get("resultCode"))) {
userAccountService.notifyWithdraw(paramMap);
} else {
log.info("提现异步回调充值失败:" + JSON.toJSONString(paramMap));
return "fail";
}
} else {
log.info("提现异步回调签名错误:" + JSON.toJSONString(paramMap));
return "fail";
}
return "success";
}
接口:UserAccountService
void notifyWithdraw(Map<String, Object> paramMap);
实现:UserAccountServiceImpl
@Transactional(rollbackFor = Exception.class)
@Override
public void notifyWithdraw(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("bindCode");
String fetchAmt = (String)paramMap.get("fetchAmt");
//根据用户账户修改账户金额
baseMapper.updateAccount(bindCode, new BigDecimal("-" + fetchAmt), new BigDecimal(0));
//增加交易流水
TransFlowBO transFlowBO = new TransFlowBO(
agentBillNo,
bindCode,
new BigDecimal(fetchAmt),
TransTypeEnum.WITHDRAW,
"提现");
transFlowService.saveTransFlow(transFlowBO);
}
二、还款
(一)需求
放款成功后,会生成借款人的还款计划与出借人的回款计划,然后借款人按照还款计划日期操作还款即可。
参考《汇付宝商户账户技术文档》3.14还款扣款,处理业务即可
(二)前端
还款按钮pages/lend/_id.vue
<td>
<a href="javascript:" @click="commitReturn(lendReturn.id)">
{{ lendReturn.status === 0 ? '还款' : '' }}
</a>
</td>
脚本pages/lend/_id.vue
commitReturn(lendReturnId) {
this.$alert(
'<div style="size: 18px;color: red;">您即将前往汇付宝确认还款</div>',
'前往汇付宝资金托管平台',
{
dangerouslyUseHTMLString: true,
confirmButtonText: '立即前往',
callback: (action) => {
if (action === 'confirm') {
this.$axios
.$post('/api/core/lendReturn/auth/commitReturn/' + lendReturnId)
.then((response) => {
document.write(response.data.formStr)
})
}
},
}
)
}
(三)后端
1、还款接口
LendReturnController
@ApiOperation("用户还款")
@PostMapping("/auth/commitReturn/{lendReturnId}")
public R commitReturn(
@ApiParam(value = "还款计划id", required = true)
@PathVariable Long lendReturnId, HttpServletRequest request) {
String token = request.getHeader("token");
Long userId = JwtUtils.getUserId(token);
String formStr = lendReturnService.commitReturn(lendReturnId, userId);
return R.ok().data("formStr", formStr);
}
接口:LendReturnService
String commitReturn(Long lendReturnId, Long userId);
实现:LendReturnServiceImpl
@Resource
private UserAccountService userAccountService;
@Resource
private LendMapper lendMapper;
@Resource
private UserBindService userBindService;
@Resource
private LendItemReturnService lendItemReturnService;
@Transactional(rollbackFor = Exception.class)
@Override
public String commitReturn(Long lendReturnId, Long userId) {
//获取还款记录
LendReturn lendReturn = baseMapper.selectById(lendReturnId);
//判断账号余额是否充足
BigDecimal amount = userAccountService.getAccount(userId);
Assert.isTrue(amount.doubleValue() >= lendReturn.getTotal().doubleValue(),
ResponseEnum.NOT_SUFFICIENT_FUNDS_ERROR);
//获取借款人code
String bindCode = userBindService.getBindCodeByUserId(userId);
//获取lend
Long lendId = lendReturn.getLendId();
Lend lend = lendMapper.selectById(lendId);
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("agentId", HfbConst.AGENT_ID);
//商户商品名称
paramMap.put("agentGoodsName", lend.getTitle());
//批次号
paramMap.put("agentBatchNo",lendReturn.getReturnNo());
//还款人绑定协议号
paramMap.put("fromBindCode", bindCode);
//还款总额
paramMap.put("totalAmt", lendReturn.getTotal());
paramMap.put("note", "");
//还款明细
List<Map<String, Object>> lendItemReturnDetailList = lendItemReturnService.addReturnDetail(lendReturnId);
paramMap.put("data", JSONObject.toJSONString(lendItemReturnDetailList));
paramMap.put("voteFeeAmt", new BigDecimal(0));
paramMap.put("notifyUrl", HfbConst.BORROW_RETURN_NOTIFY_URL);
paramMap.put("returnUrl", HfbConst.BORROW_RETURN_RETURN_URL);
paramMap.put("timestamp", RequestHelper.getTimestamp());
String sign = RequestHelper.getSign(paramMap);
paramMap.put("sign", sign);
//构建自动提交表单
String formStr = FormHelper.buildForm(HfbConst.BORROW_RETURN_URL, paramMap);
return formStr;
}
还款明细service
根据还款id获取回款列表
LendReturnService接口:
List<Map<String, Object>> addReturnDetail(Long lendReturnId);
LendReturnServiceImpl实现:
@Resource
private UserBindService userBindService;
@Resource
private LendItemMapper lendItemMapper;
@Resource
private LendMapper lendMapper;
@Resource
private LendReturnMapper lendReturnMapper;
/**
* 添加还款明细
* @param lendReturnId
*/
@Override
public List<Map<String, Object>> addReturnDetail(Long lendReturnId) {
//获取还款记录
LendReturn lendReturn = lendReturnMapper.selectById(lendReturnId);
//获取标的信息
Lend lend = lendMapper.selectById(lendReturn.getLendId());
//根据还款id获取回款列表
List<LendItemReturn> lendItemReturnList = this.selectLendItemReturnList(lendReturnId);
List<Map<String, Object>> lendItemReturnDetailList = new ArrayList<>();
for(LendItemReturn lendItemReturn : lendItemReturnList) {
LendItem lendItem = lendItemMapper.selectById(lendItemReturn.getLendItemId());
String bindCode = userBindService.getBindCodeByUserId(lendItem.getInvestUserId());
Map<String, Object> map = new HashMap<>();
//项目编号
map.put("agentProjectCode", lend.getLendNo());
//出借编号
map.put("voteBillNo", lendItem.getLendItemNo());
//收款人(出借人)
map.put("toBindCode", bindCode);
//还款金额
map.put("transitAmt", lendItemReturn.getTotal());
//还款本金
map.put("baseAmt", lendItemReturn.getPrincipal());
//还款利息
map.put("benifitAmt", lendItemReturn.getInterest());
//商户手续费
map.put("feeAmt", new BigDecimal("0"));
lendItemReturnDetailList.add(map);
}
return lendItemReturnDetailList;
}
根据还款计划id获取对应的回款计划列表
接口:
List<LendItemReturn> selectLendItemReturnList(Long lendReturnId);
实现:
@Override
public List<LendItemReturn> selectLendItemReturnList(Long lendReturnId) {
QueryWrapper<LendItemReturn> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("lend_return_id", lendReturnId);
List<LendItemReturn> lendItemReturnList = baseMapper.selectList(queryWrapper);
return lendItemReturnList;
}
2、回调接口
LendReturnController
@ApiOperation("还款异步回调")
@PostMapping("/notifyUrl")
public String notifyUrl(HttpServletRequest request) {
Map<String, Object> paramMap = RequestHelper.switchMap(request.getParameterMap());
log.info("还款异步回调:" + JSON.toJSONString(paramMap));
//校验签名
if(RequestHelper.isSignEquals(paramMap)) {
if("0001".equals(paramMap.get("resultCode"))) {
lendReturnService.notify(paramMap);
} else {
log.info("还款异步回调失败:" + JSON.toJSONString(paramMap));
return "fail";
}
} else {
log.info("还款异步回调签名错误:" + JSON.toJSONString(paramMap));
return "fail";
}
return "success";
}
接口:LendReturnService
void notify(Map<String, Object> paramMap);
实现:LendReturnServiceImpl
@Resource
private TransFlowService transFlowService;
@Resource
private UserAccountMapper userAccountMapper;
@Resource
private LendItemReturnMapper lendItemReturnMapper;
@Resource
private LendItemMapper lendItemMapper;
@Transactional(rollbackFor = Exception.class)
@Override
public void notify(Map<String, Object> paramMap) {
log.info("还款成功");
//还款编号
String agentBatchNo = (String)paramMap.get("agentBatchNo");
boolean result = transFlowService.isSaveTransFlow(agentBatchNo);
if(result){
log.warn("幂等性返回");
return;
}
//获取还款数据
String voteFeeAmt = (String)paramMap.get("voteFeeAmt");
QueryWrapper lendReturnQueryWrapper = new QueryWrapper<LendReturn>();
lendReturnQueryWrapper.eq("return_no", agentBatchNo);
LendReturn lendReturn = baseMapper.selectOne(lendReturnQueryWrapper);;
//更新还款状态
lendReturn.setStatus(1);
lendReturn.setFee(new BigDecimal(voteFeeAmt));
lendReturn.setRealReturnTime(LocalDateTime.now());
baseMapper.updateById(lendReturn);
//更新标的信息
Lend lend = lendMapper.selectById(lendReturn.getLendId());
//最后一次还款更新标的状态
if(lendReturn.getLast()) {
lend.setStatus(LendStatusEnum.PAY_OK.getStatus());
lendMapper.updateById(lend);
}
//借款账号转出金额
BigDecimal totalAmt = new BigDecimal((String)paramMap.get("totalAmt"));//还款金额
String bindCode = userBindService.getBindCodeByUserId(lend.getUserId());
userAccountMapper.updateAccount(bindCode, totalAmt.negate(), new BigDecimal(0));
//借款人交易流水
TransFlowBO transFlowBO = new TransFlowBO(
agentBatchNo,
bindCode,
totalAmt,
TransTypeEnum.RETURN_DOWN,
"借款人还款扣减,项目编号:" + lend.getLendNo() + ",项目名称:" + lend.getTitle());
transFlowService.saveTransFlow(transFlowBO);
//获取回款明细
List<LendItemReturn> lendItemReturnList = lendItemReturnService.selectLendItemReturnList(lendReturn.getId());
lendItemReturnList.forEach(item -> {
//更新回款状态
item.setStatus(1);
item.setRealReturnTime(LocalDateTime.now());
lendItemReturnMapper.updateById(item);
//更新出借信息
LendItem lendItem = lendItemMapper.selectById(item.getLendItemId());
lendItem.setRealAmount(item.getInterest());
lendItemMapper.updateById(lendItem);
//投资账号转入金额
String investBindCode = userBindService.getBindCodeByUserId(item.getInvestUserId());
userAccountMapper.updateAccount(investBindCode, item.getTotal(), new BigDecimal(0));
//投资账号交易流水
TransFlowBO investTransFlowBO = new TransFlowBO(
LendNoUtils.getReturnItemNo(),
investBindCode,
item.getTotal(),
TransTypeEnum.INVEST_BACK,
"还款到账,项目编号:" + lend.getLendNo() + ",项目名称:" + lend.getTitle());
transFlowService.saveTransFlow(investTransFlowBO);
});
}