银联支付-代付功能
功能描述
我司使用场景:业务场景,为物业公司开发物业管理平台系统,包括APP,物业管理后台等。物业费、停车费等通过APP支付后,所缴费用首先是在我司所绑定的银行卡中,然后通过集成银联支付的代付功能来完成将这些费用分发至每个物业公司的银行卡账号中(这样做的目的前提:由于申请微信商户号,务必提供一个应用如app,所有的物业公司都是使用的我司提供的app,故每个物业公司不可能各自申请一个商户号)
接入银联代付流程
接入银联代付流程链接
代付业务都是从收单机构入网的,产品是收单机构的,通道是银联的!
接入银联支付产品,您需通过收单机构向您提供支付结算服务。收单机构审核后,会有专门人员和你联系有关入网申请提交资料的相关事宜,此时你可以先让对方提供测试版的商户号、sdk jar包、商户签名私钥、ChinaPay签名公钥和开发文档api等相关资料,可以边准备所需资料边进行开发(温馨提示:接入费用一次性5000元,后期每成交一笔收取2元手续费金额不限)。
商户需要提前将钱充值到银联所提供的备付金账号(可以线上或线下),在向物业公司付费时,实质是从备付金账号到物业公司,故转账前提需要保证备付金账号的余额充足!银联方会提供查询备付金余额的接口
注意事项
通过调银联接口进行转账基本一个小时左右就可以到账,如从向银联发起支付时间算起超过三天还未到账,则可被视为超时处理,建议开一个定时任务去处理这类逻辑
API文档
查询备付金余额
参数
MERID:商户号
MERKEY_PATH:商户签名私钥路径
PUBKEY_PATH:ChinaPay签名公钥路径
public String getAccountBalance() throws Exception{
String DataRequest = MERID + VERSION;
String plainDataReturn = new String(Base64.encode(DataRequest.getBytes()));
//签名
String chkValue = null;
int KeyUsage = 0;
PrivateKey key = new PrivateKey();
key.buildKey(MERID, KeyUsage, MERKEY_PATH);
SecureLink sl = new SecureLink(key);
chkValue = sl.Sign(plainDataReturn);
HttpClient httpClient = new HttpClient();
httpClient.getHttpConnectionManager().getParams()
.setConnectionTimeout(60000);
httpClient.getHttpConnectionManager().getParams().setSoTimeout(60000);
httpClient.getParams().setParameter(HttpMethodParams.HTTP_CONTENT_CHARSET, "UTF-8");
PostMethod postMethod = new PostMethod(BALANCE_QUERY);
//填入各个表单域的值
NameValuePair[] data = {
new NameValuePair("merId", MERID),
new NameValuePair("version", VERSION),
new NameValuePair("chkValue",chkValue),
new NameValuePair("signFlag", SIGN_FLAG)
};
// 将表单的值放入postMethod中
postMethod.setRequestBody(data);
// 执行postMethod
try {
httpClient.executeMethod(postMethod);
} catch (Exception e) {
e.printStackTrace();
}
// 读取内容
InputStream resInputStream = null;
try {
resInputStream = postMethod.getResponseBodyAsStream();
} catch (IOException e) {
e.printStackTrace();
}
// 处理内容
BufferedReader reader = new BufferedReader(new InputStreamReader(resInputStream));
String tempBf = null;
StringBuffer html=new StringBuffer();
while((tempBf = reader.readLine()) != null){
html.append(tempBf);
}
String resMes = html.toString();
System.out.println("单笔查询返回报文:" + resMes);
int dex = resMes.lastIndexOf("|");
String Res_Code = resMes.substring(0,3);
//提取返回数据
if(Res_Code.equals("000")){
String Res_stat = resMes.substring(dex-2,dex-1);
String Res_chkValue = resMes.substring(dex+1);
System.out.println("Res_Code=" + Res_Code);
System.out.println("Res_stat=" + Res_stat);
System.out.println("Res_chkValue=" + Res_chkValue);
String plainData = resMes.substring(0,dex+1);
String accountBalance = plainData.substring(plainData.substring(0,dex).lastIndexOf("|")+1,dex);
System.out.println("查询账户余额:" + accountBalance);
System.out.println("需要验签的字段:" + plainData);
String Data = new String(Base64.encode(plainData.getBytes()));
System.out.println("转换成Base64后数据:" + Data);
//对收到的ChinaPay应答传回的域段进行验签
boolean buildOK = false;
boolean res = false;
try {
buildOK = key.buildKey("999999999999999", KeyUsage, PUBKEY_PATH);
} catch (Exception e) {
e.printStackTrace();
}
if (!buildOK) {
System.out.println("build error!");
return null;
}
res=sl.verifyAuthToken(Data,Res_chkValue);
System.out.println(res);
if (res){
System.out.println("验签数据正确!");
} else {
System.out.println("签名数据不匹配!");
}
return accountBalance;
} else {
String Res_chkValue = resMes.substring(dex+1);
String plainData = resMes.substring(0,dex+1);
System.out.println("需要验签的字段:" + plainData);
String Data = new String(Base64.encode(plainData.getBytes()));
System.out.println("转换成Base64后数据:" + Data);
//对收到的ChinaPay应答传回的域段进行验签
boolean buildOK = false;
boolean res = false;
try {
buildOK = key.buildKey("999999999999999", KeyUsage, PUBKEY_PATH);
} catch (Exception e) {
e.printStackTrace();
}
if (!buildOK) {
System.out.println("build error!");
return null;
}
res=sl.verifyAuthToken(Data,Res_chkValue);
System.out.println(res);
if (res){
System.out.println("验签数据正确!");
}
else {
System.out.println("签名数据不匹配!");
}
return null;
}
}
转账接口
public void transferAccount(String cashIds,String myUserName) throws Exception {
String[] cashIdArr = cashIds.split(",");
//由于银联转账每次请求响应时间不得小于60秒,故先将转账状态改为转账中,另开一个线程异步去处理转账业务逻辑
if(cashIdArr.length>0){
iChinaPayTransferAccountDao.updateTradeStatus(cashIdArr,myUserName);
}
cachedThreadPool.execute(() ->{
try{
for(String cashId : cashIdArr){
//根据提现ID查询提现金额、手续费和所转账的银行卡等信息
OperateTransferAccountVO accountVO = iChinaPayTransferAccountDao.getBankCardInfoByCashId(cashId);
//获取银联的手续费
String fee = iChinaPayTransferAccountDao.getChinaPayHandFee();
if(StringUtils.isEmpty(fee)){
fee = "2";
}
String merSeqId = LogicIdUtil.bussinessId().substring(0,16);
if(accountVO!=null){
String projectId = accountVO.getProjectId();
String total = accountVO.getAmount().subtract(accountVO.getHandlingFee()).toString();
ChinaPayTransactionBeanDTO beanDTO = new ChinaPayTransactionBeanDTO();
beanDTO.setMerId(MERID);
beanDTO.setMerDate(dateFormat.format(new Date()));
beanDTO.setMerSeqId(merSeqId);
beanDTO.setCardNo(accountVO.getBankCardNo());
beanDTO.setUserName(accountVO.getUserName());
beanDTO.setOpenBank(accountVO.getOpenBankName());
beanDTO.setProv(accountVO.getProv());
beanDTO.setCity(accountVO.getCity());
beanDTO.setTransAmt(RandomUtil.changeToFen(Double.parseDouble(total)));
beanDTO.setPurpose("转账");
beanDTO.setFlag("01");
beanDTO.setVersion(VERSION);
beanDTO.setTermType("07");
beanDTO.setPayMode("1");
String Data = beanDTO.toString();
String plainDataRequest = new String(Base64.encode(Data.getBytes()));
//签名
String chkValue = null;
int KeyUsage = 0;
PrivateKey key = new PrivateKey();
boolean flage = key.buildKey(MERID, KeyUsage, MERKEY_PATH);
if (flage == false) {
System.out.println("buildkey error!");
return;
} else {
SecureLink sl = new SecureLink(key);
chkValue = sl.Sign(plainDataRequest);
}
beanDTO.setChkValue(chkValue);
//连接Chinapay控台
HttpClient httpClient = new HttpClient();
System.out.println("HttpClient方法创建!");
httpClient.getHttpConnectionManager().getParams()
.setConnectionTimeout(60000);
httpClient.getHttpConnectionManager().getParams().setSoTimeout(60000);
//httpClient.getParams().setParameter(HttpMethodParams.HTTP_CONTENT_CHARSET, "GBK");
httpClient.getParams().setParameter(HttpMethodParams.HTTP_CONTENT_CHARSET, "UTF-8");
PostMethod postMethod = new PostMethod(CHINAPAY_PAY_URL);
System.out.println("Post方法创建!");
//填入各个表单域的值
NameValuePair[] data = {
new NameValuePair("merId", MERID),
new NameValuePair("merDate", dateFormat.format(new Date())),
new NameValuePair("merSeqId", merSeqId),
new NameValuePair("cardNo", accountVO.getBankCardNo()),
new NameValuePair("usrName", accountVO.getUserName()),
new NameValuePair("openBank", accountVO.getOpenBankName()),
new NameValuePair("prov", accountVO.getProv()),
new NameValuePair("city", accountVO.getCity()),
new NameValuePair("transAmt", RandomUtil.changeToFen(Double.parseDouble(total))),
new NameValuePair("purpose", "转账"),
new NameValuePair("flag", "01"),
new NameValuePair("version", VERSION),
new NameValuePair("chkValue", chkValue),
new NameValuePair("signFlag", "1"),
new NameValuePair("termType", "07"),
new NameValuePair("payMode", "1")
};
postMethod.setRequestBody(data);
// 执行postMethod
try {
httpClient.executeMethod(postMethod);
}catch (Exception e) {
e.printStackTrace();
}
// 读取内容
InputStream resInputStream = null;
try {
resInputStream = postMethod.getResponseBodyAsStream();
} catch (IOException e) {
e.printStackTrace();
}
// 处理内容
BufferedReader reader = new BufferedReader(new InputStreamReader(resInputStream));
String tempBf = null;
StringBuffer html = new StringBuffer();
while ((tempBf = reader.readLine()) != null) {
html.append(tempBf);
}
String resMes = html.toString();
System.out.println("交易返回报文:" + resMes);
int dex = resMes.lastIndexOf("&");
//拆分页面应答数据
String str[] = resMes.split("&");
//提取返回数据
if (str.length == 10) {
int Res_Code = str[0].indexOf("=");
int Res_merId = str[1].indexOf("=");
int Res_merDate = str[2].indexOf("=");
int Res_merSeqId = str[3].indexOf("=");
int Res_cpDate = str[4].indexOf("=");
int Res_cpSeqId = str[5].indexOf("=");
int Res_transAmt = str[6].indexOf("=");
int Res_stat = str[7].indexOf("=");
int Res_cardNo = str[8].indexOf("=");
int Res_chkValue = str[9].indexOf("=");
String responseCode = str[0].substring(Res_Code + 1);
String MerId = str[1].substring(Res_merId + 1);
String MerDate = str[2].substring(Res_merDate + 1);
String MerSeqId = str[3].substring(Res_merSeqId + 1);
String CpDate = str[4].substring(Res_cpDate + 1);
String CpSeqId = str[5].substring(Res_cpSeqId + 1);
String TransAmt = str[6].substring(Res_transAmt + 1);
String Stat = str[7].substring(Res_stat + 1);
String CardNo = str[8].substring(Res_cardNo + 1);
String ChkValue = str[9].substring(Res_chkValue + 1);
String msg = resMes.substring(0,dex);
String plainData = new String(Base64.encode(msg.getBytes()));
//对收到的ChinaPay应答传回的域段进行验签
boolean buildOK = false;
boolean res = false;
try {
buildOK = key.buildKey("999999999999999", KeyUsage, PUBKEY_PATH);
} catch (Exception e) {
e.printStackTrace();
}
if (!buildOK) {
System.out.println("build error!");
return;
}
SecureLink sl = new SecureLink(key);
res=sl.verifyAuthToken(plainData,ChkValue);
System.out.println(res);
if (res){
System.out.println("验签数据正确!");
OperateTransferPropertyPO transferPropertyPO = new OperateTransferPropertyPO(accountVO.getCashId());
if(Stat.equals("s")){
transferPropertyPO.setTradeStatus(ChinaPayTradeStatusEnum.TRADE_SUCCESS.getType());
transferPropertyPO.setTradeTime(new Date());
transferPropertyPO.setSerualNumber(merSeqId);
//保存资金变动明细 银联转账备付金变化明细
AmountChangeInfoPO changeInfoPO = new AmountChangeInfoPO(LogicIdUtil.bussinessId(),new BigDecimal(total),merSeqId, "","");
changeInfoPO.setStatus(DataStatusEnum.NORMAL.getType());
changeInfoPO.setCreateTime(new Date());
String accountBalance = this.getAccountBalance();
changeInfoPO.setTradeBeforeAmount(BigDecimal.valueOf(Double.valueOf(accountBalance)/100).add(new BigDecimal(total)).add(new BigDecimal(fee)));
changeInfoPO.setTradeAfterAmount(BigDecimal.valueOf(Double.valueOf(accountBalance)/100));
changeInfoPO.setThirdPartyPayment(2);
changeInfoPO.setTradeType(TradeTypeEnum.CHINA_PAY_FEE.getType());
changeInfoPO.setHandlingFee(new BigDecimal(fee));
iOperateBalanceAccountDao.save(SqlUtil.durableData(changeInfoPO, PlatformConstants.TABLE_SAVE));
AmountChangeInfoPO InfoPO = new AmountChangeInfoPO(LogicIdUtil.bussinessId(),new BigDecimal(total),merSeqId, "","");
InfoPO.setStatus(DataStatusEnum.NORMAL.getType());
InfoPO.setCreateTime(new Date());
InfoPO.setTradeBeforeAmount(accountVO.getTradeBeforeAmount());
InfoPO.setTradeAfterAmount(accountVO.getTradeAfterAmount());
InfoPO.setThirdPartyPayment(2);
InfoPO.setProjectId(projectId);
InfoPO.setHandlingFee(accountVO.getHandlingFee());
InfoPO.setTradeType(TradeTypeEnum.PROPERTY_WITHDRAW_CASH.getType());
iOperateBalanceAccountDao.save(SqlUtil.durableData(InfoPO, PlatformConstants.TABLE_SAVE));
//提现成功后查看该项目是否有默认使用的银行卡,如没有则将此次提现的银行卡做为默认使用银行卡
if(iOperateWithdrawDao.getDefaultUserBankCard(accountVO.getProjectId())==0){
iOperateWithdrawDao.updateDefaultUserBankCard(accountVO.getProjectId(),accountVO.getBankCardNo());
}
} else if(Stat.equals("6") || Stat.equals("9")){
transferPropertyPO.setTradeStatus(ChinaPayTradeStatusEnum.TRADE_FAIL.getType());
transferPropertyPO.setSerualNumber(merSeqId);
transferPropertyPO.setUpdateTime(new Date());
transferPropertyPO.setRemark("银行退单,交易失败");
Map<String,Object> params=new HashMap<>();
params.put("arithmeticFlag", "0");//运算: 0加,1减
params.put("amount", Double.valueOf(String.valueOf(accountVO.getAmount())));//金额
params.put("projectId", projectId);//项目编号
params.put("updateNameFlag","");//0可提现余额,1实时余额 为空都更新
messageSender.send(RabbitMQConstant.RABBITMQ_PROJECT_AMOUNT_MANAGER_QUEUE, JSON.toJSONString(params),
RabbitMQConstant.RABBITMQ_PAY_EXCHANGE,
RabbitMQConstant.RABBITMQ_PROJECT_AMOUNT_ROUTING_KEY);
}else{
transferPropertyPO.setSerualNumber(merSeqId);
transferPropertyPO.setUpdateTime(new Date());
iChinaPayTransferAccountDao.update(SqlUtil.durableData(transferPropertyPO,PlatformConstants.TABLE_UPDATE));
continue;
}
transferPropertyPO.setUpdateTime(new Date());
iChinaPayTransferAccountDao.update(SqlUtil.durableData(transferPropertyPO,PlatformConstants.TABLE_UPDATE));
}
else {
System.out.println("签名数据不匹配!");
}
}
//交易失败应答
if(str.length == 2){
int Res_Code = str[0].indexOf("=");
int Res_chkValue = str[1].indexOf("=");
String responseCode = str[0].substring(Res_Code+1);
String ChkValue = str[1].substring(Res_chkValue+1);
System.out.println("responseCode=" + responseCode);
System.out.println("chkValue=" + ChkValue);
String plainData = str[0];
String plainData1 = new String(Base64.encode(plainData.getBytes()));
System.out.println("需要验签的字段:" + plainData);
//对收到的ChinaPay应答传回的域段进行验签
boolean buildOK = false;
boolean res = false;
try {
buildOK = key.buildKey("999999999999999", KeyUsage, PUBKEY_PATH);
} catch (Exception e) {
e.printStackTrace();
}
if (!buildOK) {
System.out.println("build error!");
return;
}
SecureLink sl = new SecureLink(key);
res=sl.verifyAuthToken(plainData1,ChkValue);
System.out.println(res);
if (res){
System.out.println("验签数据正确!");
//商户备付金余额不足返回前端界面
OperateTransferPropertyPO transferPropertyPO = new OperateTransferPropertyPO(accountVO.getCashId());
transferPropertyPO.setSerualNumber(merSeqId);
transferPropertyPO.setTradeStatus(ChinaPayTradeStatusEnum.TRADE_FAIL.getType());
if(responseCode.equals("0100")) {
transferPropertyPO.setRemark("商户提交的字段长度、格式错误");
}
if(responseCode.equals("0101")) {
transferPropertyPO.setRemark("商户验签错误");
}
if(responseCode.equals("0102")) {
transferPropertyPO.setRemark("手续费计算出错");
}
if(responseCode.equals("0103")) {
transferPropertyPO.setRemark("商户备付金帐户余额不足");
}
if(responseCode.equals("0104")) {
transferPropertyPO.setRemark("操作拒绝");
}
if(responseCode.equals("0105")) {
transferPropertyPO.setRemark("重复交易");
}
transferPropertyPO.setUpdateTime(new Date());
iChinaPayTransferAccountDao.update(SqlUtil.durableData(transferPropertyPO,PlatformConstants.TABLE_UPDATE));
}
else {
System.out.println("签名数据不匹配!");
}
return;
}
}
}
}catch (Exception e){
e.printStackTrace();
}
});
}
根据流水号查询银联单笔查询接口
参数
merDate:向银联发起交易的时间
merSeqId:流水号
public String queryOrder(String merDate,String merSeqId) throws Exception{
String DataRequest = MERID + merDate + merSeqId + VERSION;
String plainDataReturn = new String(Base64.encode(DataRequest.getBytes()));
//签名
String chkValue = null;
int KeyUsage = 0;
PrivateKey key = new PrivateKey();
key.buildKey(MERID, KeyUsage, MERKEY_PATH);
SecureLink sl = new SecureLink(key);
chkValue = sl.Sign(plainDataReturn);
System.out.println("签名内容:"+ chkValue);
HttpClient httpClient = new HttpClient();
httpClient.getHttpConnectionManager().getParams()
.setConnectionTimeout(60000);
httpClient.getHttpConnectionManager().getParams().setSoTimeout(60000);
System.out.println("HttpClient方法创建!");
httpClient.getParams().setParameter(HttpMethodParams.HTTP_CONTENT_CHARSET, "UTF-8");
PostMethod postMethod = new PostMethod(QUERY_ORDER);
System.out.println("Post方法创建!");
//填入各个表单域的值
NameValuePair[] data = {
new NameValuePair("merId", MERID),
new NameValuePair("merDate", merDate),
new NameValuePair("merSeqId", merSeqId),
new NameValuePair("version", VERSION),
new NameValuePair("chkValue",chkValue),
new NameValuePair("signFlag", SIGN_FLAG)
};
// 将表单的值放入postMethod中
postMethod.setRequestBody(data);
// 执行postMethod
try {
httpClient.executeMethod(postMethod);
}catch (Exception e) {
e.printStackTrace();
}
// 读取内容
InputStream resInputStream = null;
try {
resInputStream = postMethod.getResponseBodyAsStream();
} catch (IOException e) {
e.printStackTrace();
}
// 处理内容
BufferedReader reader = new BufferedReader(new InputStreamReader(resInputStream));
String tempBf = null;
StringBuffer html=new StringBuffer();
while((tempBf = reader.readLine()) != null){
html.append(tempBf);
}
String resMes = html.toString();
System.out.println("单笔查询返回报文:" + resMes);
int dex = resMes.lastIndexOf("|");
String Res_Code = resMes.substring(0,3);
//提取返回数据
if(Res_Code.equals("000")){
String check = resMes.substring(dex-8,dex);
Pattern pattern = Pattern.compile("[0-9]*");
Matcher isNum = pattern.matcher(check);
String Res_stat ="";
if( !isNum.matches() ){
Res_stat = resMes.substring(dex-2,dex-1);
}else{
Res_stat = resMes.substring(dex-10,dex-9);
}
String Res_chkValue = resMes.substring(dex+1);
String plainData = resMes.substring(0,dex+1);
String Data = new String(Base64.encode(plainData.getBytes()));
//对收到的ChinaPay应答传回的域段进行验签
boolean buildOK = false;
boolean res = false;
try {
buildOK = key.buildKey("999999999999999", KeyUsage, PUBKEY_PATH);
} catch (Exception e) {
e.printStackTrace();
}
if (!buildOK) {
System.out.println("build error!");
}
res=sl.verifyAuthToken(Data,Res_chkValue);
System.out.println(res);
if (res){
System.out.println("验签数据正确!");
} else {
System.out.println("签名数据不匹配!");
}
return Res_stat;
} else {
String Res_chkValue = resMes.substring(dex+1);
System.out.println("Res_Code=" + Res_Code);
System.out.println("Res_chkValue=" + Res_chkValue);
String plainData = resMes.substring(0,dex+1);
String Data = new String(Base64.encode(plainData.getBytes()));
//对收到的ChinaPay应答传回的域段进行验签
boolean buildOK = false;
boolean res = false;
try {
buildOK = key.buildKey("999999999999999", KeyUsage, PUBKEY_PATH);
} catch (Exception e) {
e.printStackTrace();
}
if (!buildOK) {
System.out.println("build error!");
}
res=sl.verifyAuthToken(Data,Res_chkValue);
System.out.println(res);
if (res){
System.out.println("验签数据正确!");
}
else {
System.out.println("签名数据不匹配!");
}
}
return null;
}