提现
需求
放款成功后,借款人可以申请提现。
参考《汇付宝商户账户技术文档》3.15用户申请提现
hfb的user_account需要与srb的user_account保持统一
后端
enums
ResponseEnum.java
USER_WITHDRAW_AMOUNT_ERROR(310,"余额不足,无法提现"),
USER_AMOUNT_LESS_ZERO_ERROR(311,"提现金额必须大于0"),
controller
UserAccountController.java
@ApiOperation("提现")
@PostMapping("/auth/withdraw/{fetchAmt}")
public R withdraw(
@ApiParam(value = "提现金额",required = true)
@PathVariable BigDecimal fetchAmt, HttpServletRequest request){
String token = request.getHeader("token");
Long userId = JwtUtils.getUserId(token);
String formStr = userAccountService.withdraw(fetchAmt,userId);
return R.ok().setData("formStr",formStr);
}
@ApiOperation("提现回调")
@PostMapping("/notifyWithdraw")
public String notifyWithdraw(HttpServletRequest request){
Map<String, Object> map = RequestHelper.switchMap(request.getParameterMap());
if (RequestHelper.isSignEquals(map)) {
if ("0001".equals(map.get("resultCode"))) {
userAccountService.notifyWithdraw(map);
}else{
return "fail";
}
}else{
return "fail";
}
return "success";
}
service
UserAccountService.java
String withdraw(BigDecimal fetchAmt, Long userId);
void notifyWithdraw(Map<String, Object> map);
@Override
public String withdraw(BigDecimal fetchAmt, Long userId) {
BigDecimal amount = this.getAmount(userId);
// 余额是否大于等于提现金额
Assert.isTrue(fetchAmt.doubleValue() <= amount.doubleValue(), ResponseEnum.USER_WITHDRAW_AMOUNT_ERROR);
// 不能是负数
Assert.isTrue(fetchAmt.doubleValue() > 0, ResponseEnum.USER_AMOUNT_LESS_ZERO_ERROR);
Map<String, Object> map = new HashMap<>();
map.put("agentId", HfbConst.AGENT_ID);
map.put("agentBillNo", LendNoUtils.getWithdrawNo());
String bindCode = userBindService.getBindCodeByUserId(userId);
map.put("bindCode", bindCode);
map.put("fetchAmt", fetchAmt);
map.put("feeAmt", BigDecimal.ZERO);
map.put("notifyUrl", HfbConst.WITHDRAW_NOTIFY_URL);
map.put("returnUrl", HfbConst.WITHDRAW_RETURN_URL);
map.put("timestamp", RequestHelper.getTimestamp());
map.put("sign", RequestHelper.getSign(map));
String formStr = FormHelper.buildForm(HfbConst.WITHDRAW_URL, map);
return formStr;
}
@Override
@Transactional(rollbackFor = Exception.class)
public void notifyWithdraw(Map<String, Object> map) {
String agentBillNo = (String) map.get("agentBillNo");
boolean result = transFlowService.haveTransFlow(agentBillNo);
if (result) {
log.warn("幂等性返回");
return;
}
String bindCode = (String) map.get("bindCode");
String fetchAmt = (String) map.get("fetchAmt");
baseMapper.updateAccount(bindCode, new BigDecimal("-" + fetchAmt), BigDecimal.ZERO);
TransFlowBO transFlowBO = new TransFlowBO(
agentBillNo,
bindCode,
TransTypeEnum.WITHDRAW,
new BigDecimal(fetchAmt),
"提现"
);
transFlowService.saveTransFlow(transFlowBO);
}
前端
script
pages/user/withdraw.vue
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/withdraw/' + this.fetchAmt)
.then((response) => {
document.write(response.data.formStr)
})
}
},
}
)
},
},
放款
放款成功后,会生成借款人的还款计划与出借人的回款计划,然后借款人按照还款计划日期操作还款即可。
参考《汇付宝商户账户技术文档》3.14还款扣款,处理业务即可
需求
后端
controller
LendReturnController.java
@ApiOperation("还款")
@PostMapping("/auth/repayment/{lendReturnId}")
public R repayment(
@ApiParam(value = "还款id",required = true)
@PathVariable Long lendReturnId, HttpServletRequest request){
String token = request.getHeader("token");
Long userId = JwtUtils.getUserId(token);
String formStr = lendReturnService.repayment(lendReturnId,userId);
return R.ok().setData("formStr",formStr);
}
@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"))) {
lendReturnService.notify(map);
}else{
return "fail";
}
}else{
return "fail";
}
return "success";
}
service
LendReturnService.java
String repayment(Long lendReturnId, Long userId);
void notify(Map<String, Object> map);
LendReturnServiceImpl.java
@Resource
private LendMapper lendMapper;
@Resource
private UserBindService userBindService;
@Resource
private UserAccountService userAccountService;
@Override
public String repayment(Long lendReturnId, Long userId) {
LendReturn lendReturn = baseMapper.selectById(lendReturnId);
Assert.isTrue(userAccountService.getAmount(userId).doubleValue() >= lendReturn.getTotal().doubleValue(), ResponseEnum.NOT_SUFFICIENT_FUNDS_ERROR);
Lend lend = lendMapper.selectById(lendReturn.getLendId());
String borrowerBindCode = userBindService.getBindCodeByUserId(userId);
Map<String, Object> map = new HashMap<>();
map.put("agentId", HfbConst.AGENT_ID);
map.put("agentGoodsName", lend.getTitle());
map.put("agentBatchNo", lendReturn.getReturnNo());
map.put("fromBindCode", borrowerBindCode);
map.put("totalAmt", lendReturn.getTotal());
map.put("note", "");
List<Map<String, Object>> mapList = getLendItemReturnDetail(lendReturnId);
map.put("data", JSONObject.toJSONString(mapList));
map.put("voteFeeAmt", BigDecimal.ZERO);
map.put("returnUrl", HfbConst.BORROW_RETURN_RETURN_URL);
map.put("notifyUrl", HfbConst.BORROW_RETURN_NOTIFY_URL);
map.put("timestamp", RequestHelper.getTimestamp());
map.put("sign", RequestHelper.getSign(map));
String formStr = FormHelper.buildForm(HfbConst.BORROW_RETURN_URL, map);
return formStr;
}
@Resource
private LendItemReturnService lendItemReturnService;
@Resource
private LendItemMapper lendItemMapper;
private List<Map<String, Object>> getLendItemReturnDetail(Long lendReturnId) {
List<LendItemReturn> lendItemReturnList = lendItemReturnService.listByLendReturnId(lendReturnId);
LendReturn lendReturn = baseMapper.selectById(lendReturnId);
Lend lend = lendMapper.selectById(lendReturn.getLendId());
List<Map<String, Object>> mapList = new ArrayList<>();
lendItemReturnList.forEach(item -> {
Map<String, Object> map = new HashMap<>();
LendItem lendItem = lendItemMapper.selectById(item.getLendItemId());
String investBindCode = userBindService.getBindCodeByUserId(item.getInvestUserId());
map.put("agentProjectCode", lend.getLendNo());
map.put("voteBillNo", lendItem.getLendItemNo());
map.put("toBindCode", investBindCode);
map.put("transitAmt", item.getTotal());
map.put("baseAmt", item.getPrincipal());
map.put("benifitAmt", item.getInterest());
map.put("feeAmt",BigDecimal.ZERO);
mapList.add(map);
});
return mapList;
}
LendReturnServiceImpl.java
@Resource
private TransFlowService transFlowService;
@Resource
private UserAccountMapper userAccountMapper;
@Override
public void notify(Map<String, Object> map) {
// 幂等性判断
String agentBatchNo = map.get("agentBatchNo").toString();
boolean result = transFlowService.haveTransFlow(agentBatchNo);
if (result) {
return;
}
// 更新还款
BigDecimal voteFeeAmt = new BigDecimal(map.get("voteFeeAmt").toString());
QueryWrapper<LendReturn> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("return_no", agentBatchNo);
LendReturn lendReturn = baseMapper.selectOne(queryWrapper);
lendReturn.setStatus(1);
lendReturn.setFee(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(map.get("totalAmt").toString());
String borrowerBindCode = userBindService.getBindCodeByUserId(lendReturn.getUserId());
userAccountMapper.updateAccount(borrowerBindCode, totalAmt.negate(), BigDecimal.ZERO);
// 生成借款人交易流水
TransFlowBO transFlowBO = new TransFlowBO(
agentBatchNo,
borrowerBindCode,
TransTypeEnum.RETURN_DOWN,
totalAmt,
"借款人还款,项目编号:" + lend.getLendNo() + ",项目名称:" + lend.getTitle()
);
transFlowService.saveTransFlow(transFlowBO);
List<LendItemReturn> lendItemReturnList = lendItemReturnService.listByLendReturnId(lendReturn.getId());
lendItemReturnList.forEach(item -> {
// 更新回款
item.setStatus(1);
item.setRealReturnTime(LocalDateTime.now());
lendItemReturnService.updateById(item);
// 更新投资记录
LendItem lendItem = lendItemMapper.selectById(item.getLendItemId());
lendItem.setRealAmount(lendItem.getRealAmount().add(item.getInterest()));
lendItemMapper.updateById(lendItem);
// 更新投资人账户
String investBindCode = userBindService.getBindCodeByUserId(item.getInvestUserId());
userAccountMapper.updateAccount(investBindCode,item.getTotal(),BigDecimal.ZERO);
// 生成投资人交易流水
TransFlowBO investTransFlow = new TransFlowBO(
LendNoUtils.getReturnItemNo(),
investBindCode,
TransTypeEnum.INVEST_BACK,
item.getTotal(),
"投资人回款,项目编号:" + lend.getLendNo() + ",项目名称:" + lend.getTitle()
);
transFlowService.saveTransFlow(investTransFlow);
});
}
前端
script
pages/lend/_id.vue
methods: {
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/repayment/' + lendReturnId)
.then((response) => {
document.write(response.data.formStr)
})
}
},
}
)
},
测试各个表的变化
尚融宝
lend_return
fee:手续费没设置,所以是0
第一次还款
第二次还款
第三次还款
lend
status:还款中是2,还完最后一次,会修改状态
第一次还款
第二次还款
第三次还款
trans_flow
user_account
还款之前
第一次还款
第二次还款
第三次还款
lend_item
第一次还款
第二次还款
第三次还款
lend_item_return
第一次还款
第二次还款
第三次还款
汇付宝
user_account
user_return
user_item_return