hfb.sql
hfb
application-dev.yml
中修改数据库配置
端口:9999
参考《汇付宝商户账户技术文档》业务接口3.1参数说明
参考《汇付宝商户账户技术文档》业务接口3.2返回/通知结果
参考《汇付宝商户账户技术文档》业务接口3.3账户绑定
step1:用户在个人中心点击 “立即开通” http://localhost:3000/user/bind
step2:尚融宝展示账户绑定页面
step3:用户填写基本信息(注意:身份证必须是尚未开户),点击“开户”按钮
step4:尚融宝后台创建用户绑定信息(新建user_bind记录)
step5:跳转到汇付宝页面(资金托管接口调用)
util
包引入HttpUtils.java
工具类
这个工具类使用了另一个常见的远程连接工具:java.net.HttpURLConnection
package com.indi.common.util;
import lombok.extern.slf4j.Slf4j;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
/**
*
*/
@Slf4j
public final class HttpUtils {
static final String POST = "POST";
static final String GET = "GET";
static final int CONN_TIMEOUT = 30000;// ms
static final int READ_TIMEOUT = 30000;// ms
/**
* post 方式发送http请求.
*
* @param strUrl
* @param reqData
* @return
*/
public static byte[] doPost(String strUrl, byte[] reqData) {
return send(strUrl, POST, reqData);
}
/**
* get方式发送http请求.
*
* @param strUrl
* @return
*/
public static byte[] doGet(String strUrl) {
return send(strUrl, GET, null);
}
/**
* @param strUrl
* @param reqmethod
* @param reqData
* @return
*/
public static byte[] send(String strUrl, String reqmethod, byte[] reqData) {
try {
URL url = new URL(strUrl);
HttpURLConnection httpcon = (HttpURLConnection) url.openConnection();
httpcon.setDoOutput(true);
httpcon.setDoInput(true);
httpcon.setUseCaches(false);
httpcon.setInstanceFollowRedirects(true);
httpcon.setConnectTimeout(CONN_TIMEOUT);
httpcon.setReadTimeout(READ_TIMEOUT);
httpcon.setRequestMethod(reqmethod);
httpcon.connect();
if (reqmethod.equalsIgnoreCase(POST)) {
OutputStream os = httpcon.getOutputStream();
os.write(reqData);
os.flush();
os.close();
}
BufferedReader in = new BufferedReader(new InputStreamReader(httpcon.getInputStream(),"utf-8"));
String inputLine;
StringBuilder bankXmlBuffer = new StringBuilder();
while ((inputLine = in.readLine()) != null) {
bankXmlBuffer.append(inputLine);
}
in.close();
httpcon.disconnect();
return bankXmlBuffer.toString().getBytes();
} catch (Exception ex) {
log.error(ex.toString(), ex);
return null;
}
}
/**
* 从输入流中读取数据
*
* @param inStream
* @return
* @throws Exception
*/
public static byte[] readInputStream(InputStream inStream) throws Exception {
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = 0;
while ((len = inStream.read(buffer)) != -1) {
outStream.write(buffer, 0, len);
}
byte[] data = outStream.toByteArray();// 网页的二进制数据
outStream.close();
inStream.close();
return data;
}
}
添加依赖
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
dependency>
创建
hfb
包,添加辅助类
FormHelper
:构建表单工具
package com.indi.srb.core.hfb;
import java.util.Iterator;
import java.util.Map;
public class FormHelper {
/**
* 构建自动提交form表单
* @param url 表单提交的url
* @param paramMap 表单的提交项
* @return
*/
public static String buildForm(String url, Map<String, Object> paramMap) {
StringBuffer inputStr = new StringBuffer();
Iterator<Map.Entry<String, Object>> entries = paramMap.entrySet().iterator();
while(entries.hasNext()){
Map.Entry<String, Object> entry = entries.next();
String key = entry.getKey();
Object value = entry.getValue();
inputStr.append("");
}
String formStr = "\n" +
"\n" +
"\n" +
"\n" +
"\n" +
"\n" +
"\n" +
"\n" +
"";
return formStr;
}
}
HfbConst
:常量定义
package com.indi.srb.core.hfb;
/**
* 汇付宝常量定义
*/
public class HfbConst {
//给商户分配的唯一标识
public static final String AGENT_ID = "999888";
//签名key
public static final String SIGN_KEY = "9876543210";
/**
* 用户绑定
*/
//用户绑定汇付宝平台url地址
public static final String USERBIND_URL = "http://localhost:9999/userBind/BindAgreeUserV2";
//用户绑定异步回调
public static final String USERBIND_NOTIFY_URL = "http://localhost/api/core/userBind/notify";
//用户绑定同步回调
public static final String USERBIND_RETURN_URL = "http://localhost:3000/user";
/**
* 充值
*/
//充值汇付宝平台url地址
public static final String RECHARGE_URL = "http://localhost:9999/userAccount/AgreeBankCharge";
//充值异步回调
public static final String RECHARGE_NOTIFY_URL = "http://localhost/api/core/userAccount/notify";
//充值同步回调
public static final String RECHARGE_RETURN_URL = "http://localhost:3000/user";
/**
* 投标
*/
//充值汇付宝平台url地址
public static final String INVEST_URL = "http://localhost:9999/userInvest/AgreeUserVoteProject";
//充值异步回调
public static final String INVEST_NOTIFY_URL = "http://localhost/api/core/lendItem/notify";
//充值同步回调
public static final String INVEST_RETURN_URL = "http://localhost:3000/user";
/**
* 放款
*/
public static final String MAKE_LOAD_URL = "http://localhost:9999/userInvest/AgreeAccountLendProject";
/**
* 提现
*/
//充值汇付宝平台url地址
public static final String WITHDRAW_URL = "http://localhost:9999/userAccount/CashBankManager";
//充值异步回调
public static final String WITHDRAW_NOTIFY_URL = "http://localhost/api/core/userAccount/notifyWithdraw";
//充值同步回调
public static final String WITHDRAW_RETURN_URL = "http://localhost:3000/user";
/**
* 还款扣款
*/
//充值汇付宝平台url地址
public static final String BORROW_RETURN_URL = "http://localhost:9999/lendReturn/AgreeUserRepayment";
//充值异步回调
public static final String BORROW_RETURN_NOTIFY_URL = "http://localhost/api/core/lendReturn/notifyUrl";
//充值同步回调
public static final String BORROW_RETURN_RETURN_URL = "http://localhost:3000/user";
}
RequestHelper.java
:签名工具
package com.indi.srb.core.hfb;
import com.alibaba.fastjson.JSONObject;
import com.indi.common.util.HttpUtils;
import com.indi.common.util.MD5;
import lombok.extern.slf4j.Slf4j;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
@Slf4j
public class RequestHelper {
public static void main(String[] args) {
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("d", "4");
paramMap.put("b", "2");
paramMap.put("c", "3");
paramMap.put("a", "1");
}
/**
* 请求数据获取签名
* @param paramMap
* @return
*/
public static String getSign(Map<String, Object> paramMap) {
if(paramMap.containsKey("sign")) {
paramMap.remove("sign");
}
TreeMap<String, Object> sorted = new TreeMap<>(paramMap);
StringBuilder str = new StringBuilder();
for (Map.Entry<String, Object> param : sorted.entrySet()) {
str.append(param.getValue()).append("|");
}
str.append(HfbConst.SIGN_KEY);
log.info("加密前:" + str.toString());
String md5Str = MD5.encrypt(str.toString());
log.info("加密后:" + md5Str);
return md5Str;
}
/**
* Map转换
* @param paramMap
* @return
*/
public static Map<String, Object> switchMap(Map<String, String[]> paramMap) {
Map<String, Object> resultMap = new HashMap<>();
for (Map.Entry<String, String[]> param : paramMap.entrySet()) {
resultMap.put(param.getKey(), param.getValue()[0]);
}
return resultMap;
}
/**
* 签名校验
* @param paramMap
* @return
*/
public static boolean isSignEquals(Map<String, Object> paramMap) {
String sign = (String)paramMap.get("sign");
String md5Str = getSign(paramMap);
if(!sign.equals(md5Str)) {
return false;
}
return true;
}
/**
* 获取时间戳
* @return
*/
public static long getTimestamp() {
return new Date().getTime();
}
/**
* 封装同步请求
* @param paramMap
* @param url
* @return
*/
public static JSONObject sendRequest(Map<String, Object> paramMap, String url){
String result = "";
try {
//封装post参数
StringBuilder postdata = new StringBuilder();
for (Map.Entry<String, Object> param : paramMap.entrySet()) {
postdata.append(param.getKey()).append("=")
.append(param.getValue()).append("&");
}
log.info(String.format("--> 发送请求到汇付宝:post data %1s", postdata));
byte[] reqData = postdata.toString().getBytes("utf-8");
byte[] respdata = HttpUtils.doPost(url,reqData);
result = new String(respdata);
log.info(String.format("--> 汇付宝应答结果:result data %1s", result));
} catch (Exception ex) {
ex.printStackTrace();
}
return JSONObject.parseObject(result);
}
}
还有一些枚举类,太多了,我就不添加了,看看以后传到gitee上吧
UserBindVO.java
package com.indi.srb.core.pojo.vo;
@Data
@ApiModel(description = "账户绑定")
public class UserBindVO {
@ApiModelProperty(value = "用户姓名")
private String name;
@ApiModelProperty(value = "身份证号")
private String idCard;
@ApiModelProperty(value = "银行卡号")
private String bankNo;
@ApiModelProperty(value = "银行类型")
private String bankType;
@ApiModelProperty(value = "手机号")
private String mobile;
}
UserBindService.java
String commitBindUser(UserBindVO userBindVO,Long userId);
UserBindServiceImpl.java
@Transactional(rollbackFor = Exception.class)
@Override
public String commitBindUser(UserBindVO userBindVO, Long userId) {
// 查询其他人的绑定信息
QueryWrapper<UserBind> queryWrapper = new QueryWrapper<>();
queryWrapper
.eq("id_card", userBindVO.getIdCard())
.ne("user_id", userId);
UserBind userBind = baseMapper.selectOne(queryWrapper);
// 有没有人用过这个身份证号码
Assert.isNull(userBind, ResponseEnum.USER_BIND_IDCARD_EXIST_ERROR);
// 查询当前用户绑定信息
queryWrapper = new QueryWrapper<>();
queryWrapper.eq("user_id", userId);
userBind = baseMapper.selectOne(queryWrapper);
// 判断是否有绑定记录
if (userBind == null) {
// 没有则创建
userBind = new UserBind();
// 把用户填的类信息拷贝到绑定表对应的实体类
BeanUtils.copyProperties(userBindVO,userBind);
userBind.setId(userId);
userBind.setStatus(UserBindEnum.NO_BIND.getStatus());
baseMapper.insert(userBind);
}else{
// 有则更新
BeanUtils.copyProperties(userBindVO,userBind);
baseMapper.updateById(userBind);
}
// 创建自动提交表单
Map<String, Object> map = new HashMap<>();
map.put("agentId", HfbConst.AGENT_ID);
map.put("agentUserId",userBind.getUserId());
map.put("personalName",userBindVO.getName());
map.put("idCard",userBindVO.getIdCard());
map.put("bankNo",userBindVO.getBankNo());
map.put("bankType",userBindVO.getBankType());
map.put("mobile",userBindVO.getMobile());
map.put("returnUrl",HfbConst.USERBIND_RETURN_URL);
map.put("notifyUrl",HfbConst.USERBIND_NOTIFY_URL);
map.put("timestamp", RequestHelper.getTimestamp());
map.put("sign",RequestHelper.getSign(map));
String formStr = FormHelper.buildForm(HfbConst.USERBIND_URL, map);
return formStr;
}
UserBindController.java
@Api(tags = "用户认证")
@Slf4j
@RestController
@RequestMapping("/api/core/userBind")
public class UserBindController {
@Resource
private UserBindService userBindService;
@ApiOperation("账户绑定提交数据")
@PostMapping("/auth/bind")
public R bind(@RequestBody UserBindVO userBindVO, HttpServletRequest request){
String token = request.getHeader("token");
Long userId = JwtUtils.getUserId(token);
String formStr = userBindService.commitBindUser(userBindVO, userId);
return R.ok().setData("formStr",formStr);
}
}
pages/user/bind.vue
commitBind() {
this.$alert(
'您即将前往汇付宝绑定账号',
'前往汇付宝资金托管平台',
{
dangerouslyUseHTMLString: true,
confirmButtonText: '立即前往',
callback: (action) => {
if (action === 'confirm') {
this.$axios
.$post('/api/core/userBind/auth/bind', this.userBind)
.then((response) => {
document.write(response.data.formStr)
})
}
},
}
)
},
尚融宝应该在网站引导客户在“资金托管平台“开设账户。也就是说投资人和借款人必须在资金托管平台页面上自主开户,与尚融宝建立对应的账户体系。
以后所有的资金操作都会到“资金托管平台”进行,绑定账户流程如图:
srb:
step6:汇付宝验证用户身份、用户设置交易密码
step7:点击确定汇付宝创建绑定账号(新建user_bind和user_account记录)
step8:异步回调:
尚融宝user_bind表更新bind_code字段、status字段
尚融宝user_info表更新 bind_code字段、name字段、idCard字段、bind_status字段
step9:用户点击“返回平台”,返回尚融宝
UserInfo.java
/**
* 用户完成汇付宝认证时使用
*
*/
public UserInfo(Long id, String name, String idCard, Integer bindStatus, String bindCode) {
this.id = id;
this.name = name;
this.idCard = idCard;
this.bindStatus = bindStatus;
this.bindCode = bindCode;
}
UserBindService.java
void notify(Map<String,Object> map);
UserBindServiceImpl.java
@Transactional(rollbackFor = Exception.class)
@Override
public void notify(Map<String, Object> map) {
String bindCode = (String) map.get("bindCode");
String agentUserId = (String) map.get("agentUserId");
// 更新用户绑定表:绑定状态、绑定协议
QueryWrapper<UserBind> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("user_id", agentUserId);
UserBind userBind = baseMapper.selectOne(queryWrapper);
userBind.setBindCode(bindCode);
userBind.setStatus(UserBindEnum.BIND_OK.getStatus());
baseMapper.updateById(userBind);
// 更新用户表:用户名称、身份证、绑定状态、绑定协议
UserInfo userInfo = new UserInfo(
new Long(agentUserId),
userBind.getName(),
userBind.getIdCard(),
UserBindEnum.BIND_OK.getStatus(),
bindCode
);
userInfoMapper.updateById(userInfo);
}
UserBindController.java
@ApiOperation("绑定之后的回调")
@PostMapping("/notify")
public String notify(HttpServletRequest request){
Map<String, Object> map = RequestHelper.switchMap(request.getParameterMap());
log.info("用户账号绑定异步回调:"+ JSON.toJSONString(map));
if (!RequestHelper.isSignEquals(map)){
log.error("用户账号绑定异步回调签名错误:"+JSON.toJSONString(map));
return "fail";
}
userBindService.notify(map);
return "success";
}