Java实现建行聚合支付对接及其回调
前言
本文原文基础上,进行完善而成
原文链接 java实现对接建行支付及其回调
其他可参考链接 集成建行聚合支付踩过的坑,有些槽不吐不快
产生订单
/**
* 验签用,相关包找银行要
*/
private RSASig rsaSig = new RSASig();
/**
* 商户代码,固定写死的,找银行客户经理要
*/
@Value("${ccb.MERCHANTID}")
private String MERCHANTID;
/**
* 商户柜台代码, 这里用yml会报错,固定写死即可,找银行客户经理要
*/
private String POSID = "xxxxxxxxx";
/**
* 分行代码,固定写死的,找银行客户经理要
*/
@Value("${ccb.BRANCHID}")
private String BRANCHID;
/**
* 完整公钥,验签用,建行官网"商户服务平台"下载"E路护航"获取
*/
@Value("${ccb.PUBLICKEY}")
private String PUBLICKEY;
/**
* 公钥后30位
*/
@Value("${ccb.PUB}")
private String PUB;
/**
* 交易码 这个参数的值是固定的,不可以修改
*/
@Value("${ccb.TXCODE}")
private String TXCODE;
@ResponseBody
@PostMapping("ccbPay")
public JSONObject ccbPay(String id, String money) {
/**
* 生成线上订单
*/
String PAYMENT = money;
StringBuffer tmp = new StringBuffer();
tmp.append("MERCHANTID=").append(MERCHANTID);
tmp.append("&POSID=").append(POSID);
tmp.append("&BRANCHID=").append(BRANCHID);
tmp.append("&ORDERID=").append(ORDERID);
tmp.append("&PAYMENT=").append(PAYMENT);
tmp.append("&CURCODE=01");
tmp.append("&TXCODE=").append(TXCODE);
tmp.append("&REMARK1=&REMARK2=");
tmp.append("&RETURNTYPE=3").append("&TIMEOUT=");
tmp.append("&PUB=").append(PUB);
Map map = new HashMap();
map.put("CCB_IBSVersion", "V6");//必输项
map.put("MERCHANTID", MERCHANTID);
map.put("BRANCHID", BRANCHID);
map.put("POSID", POSID);
map.put("ORDERID", ORDERID);
map.put("PAYMENT", PAYMENT);
map.put("CURCODE", "01");
map.put("TXCODE", TXCODE);
map.put("REMARK1", "");
map.put("REMARK2", "");
map.put("RETURNTYPE", "3");
map.put("TIMEOUT", "");
map.put("MAC", MD5.md5Str(tmp.toString()));
// 这个url是建设银行指定的,尽量不要换
String ret = HttpClientUtil.httpPost("https://ibsbjstar.ccb.com.cn/CCBIS/ccbMain?CCB_IBSVersion=V6", map);
QrURLDemo qrURLDemo = JSON.parseObject(ret, QrURLDemo.class);
// 这个url触发get请求会获取到一个新的页面
String s = HttpClientUtil.httpGet(qrURLDemo.getPAYURL(), "UTF-8");
// 获取QRURL
QrURLDemo qrURLDemo1 = JSON.parseObject(s, QrURLDemo.class);
String decode = "";
try {
decode = URLDecoder.decode(qrURLDemo1.getQRURL(), "UTF-8");
} catch (Exception e) {
e.printStackTrace();
}
String code = qrURLDemo1.getSUCCESS();
JSONObject json = new JSONObject();
// 安卓通过这个url就可以支付了
json.put("decode", decode); //返回的这个decode就是支付所需跳转的url
json.put("code", code); //这个是状态码
// 还会返回其他东西,可打断点,按需取
return json;
}
yml配置文件
ccb:
MERCHANTID: # 商户代码,固定写死的,找银行客户经理要
BRANCHID: # 分行代码,固定写死的,找银行客户经理要
TXCODE: 530550 # 根据支付方式,写死
PUB: # 公钥后30位
PUBLICKEY: # 完整公钥
说明
decode:就是支付二维码的链接。可在微信、支付宝中直接跳转支付页面,也可转换为二维码扫码跳转支付页面
银行回调
支付完成后,建行会自动调用回调地址(在建行官网商户平台配置,银行的客户经理也能配置),分为页面回调和服务器回调
-
页面反馈(方法:get):付款人付款完成后,点击“返回商户网站”按钮,触发页面反馈
-
服务器反馈(方法:post):只要支付成功,无需触发,由建行支付网关,以post 方法,发信息给反馈URL
/**
* 支付页面回调(页面反馈 get)付款人付款完成后,点击“返回商户网站”按钮,触发页面反馈
*
* @return
*/
@GetMapping("/payCallBackForPage")
@ResponseBody
public String payCallBackForPage(PayCallBackEntity payCallBackEntity, HttpServletResponse response) throws Exception {
// 本人直接忽略页面回调,需要的话和服务器回调代码类似
return "SUCCESS";
}
/**
* 支付服务器回调(服务器反馈 Post)付款人付款完成后,触发服务器反馈
*
* @return
*/
@PostMapping("/payCallBackForServer")
@ResponseBody
public String payCallBackForServer(PayCallBackEntity payCallBackEntity, HttpServletResponse response) throws Exception {
System.out.println("payCallBackEntity = " + payCallBackEntity);
// 验签
rsaSig.setPublicKey(PUBLICKEY);
String src = "POSID=" + payCallBackEntity.getPOSID() + "&BRANCHID=" + payCallBackEntity.getBRANCHID() + "&ORDERID=" + payCallBackEntity.getORDERID()
+ "&PAYMENT=" + payCallBackEntity.getPAYMENT() + "&CURCODE=" + payCallBackEntity.getCURCODE() + "&REMARK1=" + payCallBackEntity.getREMARK1()
+ "&REMARK2=" + payCallBackEntity.getREMARK2() + "&ACC_TYPE=" + payCallBackEntity.getACC_TYPE() + "&SUCCESS=" + payCallBackEntity.getSUCCESS();
// 验签结果
boolean signResult = rsaSig.verifySigature(payCallBackEntity.getSIGN(), src);
if (!signResult) {
System.out.println("验签失败!");
return "SUCCESS";
}
String success = payCallBackEntity.getSUCCESS();
String orderId = payCallBackEntity.getORDERID();
String money = payCallBackEntity.getPAYMENT();
System.out.println("success: -" + success);
System.out.println("orderId: -" + orderId);
if ("Y".equals(success)) {
// 此处最好调用查询接口获取XML文件,然后通过XML校验是否支付成功,本人尝试多次未果,有兴趣可参考前言中"原文链接"尝试
// 更新运单中的支付状态 更新支付状态 记录收款日志
// 其他操作自行添加
} else {
System.out.println("支付失败");
}
// 不论支付成功失败,给银行一个返回结果
return "SUCCESS";
}
支付回调实体类
@Data
public class PayCallBackEntity{
private String POSID; //商户柜台代码
private String BRANCHID;//分行代码
private String ORDERID; //定单号
private String PAYMENT; //付款金额
private String CURCODE; //币种
private String REMARK1; //备注一
private String REMARK2; //备注二
private String ACC_TYPE; //账户类型 服务器通知中有此字段返回且参与验签
private String SUCCESS; //成功标志 成功-Y,失败-N
private String TYPE; //接口类型 分行业务人员在P2员工渠道后台设置防钓鱼的开关。 1.开关关闭时,无此字段返回且不参与验签 2.开关打开时,有此字段返回且参与验签。参数值为 1-防钓鱼接口
private String REFERER; //Referer信息 分行业务人员在P2员工渠道后台设置防钓鱼开关。 1.开关关闭时,无此字段返回且不参与验签。 2.开关打开时,有此字段返回且参与验签
private String CLIENTIP; //客户端IP 分行业务人员在P2员工渠道后台设置防钓鱼的开关。 1.开关关闭时,无此字段返回且不参与验签 2.开关打开时,有此字段返回且参与验签。参数值为 客户在建行系统中的IP
private String ACCDATE; //系统记账日期 商户登陆商户后台设置返回记账日期的开关 1.开关关闭时,无此字段返回且不参与验签。 2.开关打开时,有此字段返回且参与验签。参数值格式为YYYYMMDD(如20100907)。
private String USRMSG; //支付账户信息 分行业务人员在P2员工渠道后台设置防钓鱼开关和返回账户信息的开关。 1.开关关闭时,无此字段返回且不参与验签。2.开关打开但支付失败时,无此字段返回且不参与验签。3.开关打开且支付成功时,有此字段返回且参与验签。无PAYTYPE返回时,参数值格式如下:“姓名|账号加密后的密文”。有PAYTYPE返回时,该参数值为空。
private String USRINFO; //客户加密信息 分行业务人员在P2员工渠道后台设置防钓鱼开关和客户信息加密返回的开关。 1.开关关闭时,无此字段返回且不参与验签
private String PAYTYPE; //支付方式 ALIPAY:支付宝 WEIXIN:微信 为空:建行龙支付 该字段有返回时参与验签,无此字段返回时不参与验签。
private String SIGN; //数字签名
}
httpclient工具类
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.NameValuePair;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.params.HttpMethodParams;
import java.io.IOException;
import java.util.Iterator;
import java.util.Map;
public class HttpClientUtil {
public static String httpReader(String url, String code){
System.out.println("GetPage:"+url);
HttpClient client = new HttpClient();
GetMethod method = new GetMethod(url);
String result = null;
try {
client.executeMethod(method);
int status = method.getStatusCode();
if (status == HttpStatus.SC_OK) {
result = method.getResponseBodyAsString();
} else {
System.out.println("Method failed: " + method.getStatusLine());
}
} catch (HttpException e) {
System.out.println("Please check your provided http address!");
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally{
if(method!=null)method.releaseConnection();
method = null;
client = null;
}
return result;
}
public static String httpGet(String url,String code) {
System.out.println("GetPage:"+url);
String content = null;
HttpClient httpClient = new HttpClient();
httpClient.getParams().setParameter(HttpMethodParams.USER_AGENT,"Mozilla/5.0 (X11; U; Linux i686; zh-CN; rv:1.9.1.2) Gecko/20090803 Fedora/3.5.2-2.fc11 Firefox/3.5.2");
GetMethod method = new GetMethod(url);
try {
int statusCode = httpClient.executeMethod(method);
System.out.println("httpClientUtils::statusCode="+statusCode);
System.out.println(method.getStatusLine());
content = new String(method.getResponseBody(), code);
} catch (Exception e) {
System.out.println("time out");
e.printStackTrace();
} finally {
if(method!=null)method.releaseConnection();
method = null;
httpClient = null;
}
return content;
}
public static String httpPost(String url, Map paramMap, String code) {
System.out.println("GetPage:"+url);
String content = null;
if (url == null || url.trim().length() == 0 || paramMap == null
|| paramMap.isEmpty())
return null;
HttpClient httpClient = new HttpClient();
httpClient.getParams().setParameter(HttpMethodParams.USER_AGENT,"Mozilla/5.0 (X11; U; Linux i686; zh-CN; rv:1.9.1.2) Gecko/20090803 Fedora/3.5.2-2.fc11 Firefox/3.5.2");//
PostMethod method = new PostMethod(url);
Iterator it = paramMap.keySet().iterator();
while (it.hasNext()) {
String key = it.next() + "";
Object o = paramMap.get(key);
if (o != null && o instanceof String) {
method.addParameter(new NameValuePair(key, o.toString()));
}
if (o != null && o instanceof String[]) {
String[] s = (String[]) o;
if (s != null)
for (int i = 0; i < s.length; i++) {
method.addParameter(new NameValuePair(key, s[i]));
}
}
}
try {
int statusCode = httpClient.executeMethod(method);
System.out.println("httpClientUtils::statusCode="+statusCode);
System.out.println(method.getStatusLine());
content = new String(method.getResponseBody(), code);
} catch (Exception e) {
System.out.println("time out");
e.printStackTrace();
} finally {
if(method!=null)method.releaseConnection();
method = null;
httpClient = null;
}
return content;
}
public static String httpPost(String url, Map paramMap) {
return HttpClientUtil.httpPost(url, paramMap, "UTF-8");
}
}
MD5工具类
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class MD5 {
public static String md5Str(String str) {
if (str == null) return "";
return md5Str(str, 0);
}
public static String md5Str(String str, int offset) {
try {
MessageDigest md5 = MessageDigest.getInstance("MD5");
byte[] b = str.getBytes("UTF8");
md5.update(b, offset, b.length);
return byteArrayToHexString(md5.digest());
} catch (NoSuchAlgorithmException ex) {
ex.printStackTrace();
return null;
} catch (UnsupportedEncodingException ex) {
ex.printStackTrace();
return null;
}
}
/**
* @param b byte[]
* @return String
*/
public static String byteArrayToHexString(byte[] b) {
String result = "";
for (int i = 0; i < b.length; i++) {
result += byteToHexString(b[i]);
}
return result;
}
private static String[] hexDigits =
{
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b",
"c", "d", "e", "f"};
public static String byteToHexString(byte b) {
int n = b;
if (n < 0) {
n = 256 + n;
}
int d1 = n / 16;
int d2 = n % 16;
return hexDigits[d1] + hexDigits[d2];
}
}
建行无感支付实体类
/**
* 建行无感支付实体类
*/
public class QrURLDemo {
private String SUCCESS;
private String PAYURL;
private String QRURL; //安卓点这个会直接跳到支付页面
}
后记
- 关于查询接口获取xml文件,然后进行二次校验的操作,有成功的大佬请私信或留言交流一下,本人尝试多次未果
- 其他问题留言