java实现对接建行支付及其回调 您所在的位置:网站首页 建设银行网银错误代码查询 java实现对接建行支付及其回调

java实现对接建行支付及其回调

2024-07-11 22:16| 来源: 网络整理| 查看: 265

最近公司要对接建行支付,也是查了很多资料,走了很多弯路,还问了建行的技术员,现把它记录下来,直接上代码。

调用支付所需常量

@Value("${ccb.MERCHANTID}") private String MERCHANTID; //商户代码,固定写死的,需要申请 //@Value("${ccb.POSID}") //这里用yml提取出来就会报错.. private String POSID = "xxxxx"; //商户柜台代码,固定写死的,需要申请 @Value("${ccb.BRANCHID}") private String BRANCHID; //分行代码,固定写死的,需要申请 @Value("${ccb.PUB32TR2}") private String PUB32TR2; //公钥后30位 private String BEGORDERID = ""; private String ENDORDERID = ""; private String QUPWD = "xxxxx";//这里写建行支付的商户密码 //交易码 这个参数的值是固定的,不可以修改 private String TXCODE = "xxxxx"; /*必输项 1页面形式 2文件返回形式 (提供TXT和XML格式文件的下载) 3 XML页面形式*/ private String SEL_TYPE = "3"; /*不知道干嘛用的*/ private String CHANNEL = ""; /*不知道干嘛用的*/ private String OPERATOR = ""; @Autowired private JinshiCCBPayMapper jinshiCCBPayMapper; @Override public JSONObject CCBPay(JSONObject jsonObject) { String CURCODE = "01"; //付款币种,固定写01 代表支付金额 String TXCODE = "530550"; //由建行统一分配为530550 String REMARK1 = ""; String REMARK2 = ""; String RETURNTYPE = "3"; // 返回类型,固定参数是3 ,代表是返回带url的支付信息 String TIMEOUT = ""; String ORDERID = String.valueOf(jsonObject.get("orderId")); //订单号 由商户提供,最长40位,不能重复 String PAYMENT = String.valueOf(jsonObject.get("payment"));//支付金额 StringBuffer tmp = new StringBuffer(); tmp.append("MERCHANTID="); tmp.append(MERCHANTID); tmp.append("&POSID="); tmp.append(POSID); tmp.append("&BRANCHID="); tmp.append(BRANCHID); tmp.append("&ORDERID="); tmp.append(ORDERID); tmp.append("&PAYMENT="); tmp.append(PAYMENT); tmp.append("&CURCODE="); tmp.append(CURCODE); tmp.append("&TXCODE="); tmp.append(TXCODE); tmp.append("&REMARK1="); tmp.append(REMARK1); tmp.append("&REMARK2="); tmp.append(REMARK2); tmp.append("&RETURNTYPE="); tmp.append(RETURNTYPE); tmp.append("&TIMEOUT="); tmp.append(TIMEOUT); tmp.append("&PUB="); tmp.append(PUB32TR2); 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", CURCODE); map.put("TXCODE", TXCODE); map.put("REMARK1", REMARK1); map.put("REMARK2", REMARK2); map.put("RETURNTYPE", RETURNTYPE); map.put("TIMEOUT", 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 = URLDecoder.decode(qrURLDemo1.getQRURL()); String code = qrURLDemo1.getSUCCESS(); // 安卓通过这个url就可以支付了 JSONObject json = new JSONObject(); json.put("decode",decode); //返回的这个decode就是支付所需跳转的url json.put("code",code); //这个是状态码 //还会返回其他东西,我这里只需要这两个,所以只写了url和code return json; } 附 yml文件里配置的参数 ccb: # 商户代码,固定写死的,需要申请 MERCHANTID: xxxxxxx POSID: xxxxxxx # 商户柜台代码,固定写死的,需要申请 BRANCHID: xxxxxxx # 分行代码,固定写死的,需要申请 CURCODE: xxxxxxx #付款币种,固定写01 代表支付金额 TXCODE: xxxxxxx # 由建行统一分配为530550 PUB32TR2: xxxxxxx # 公钥后30位

微信支付界面 在这里插入图片描述 支付完成后,建行会自动调用回调地址,这个地址是在建行商户平台配置的,反馈有两种,网页反馈(方法:get)和服务器反馈(方法:post),请看下图 页面反馈:付款人付款完成后,点击“返回商户网站”按钮,触发页面反馈 服务器反馈:只要支付成功,无需触发,由建行支付网关,以post 方法,发信息给反馈URL 还分为网上银行和手机银行反馈,网上银行就是微信支付宝调用url支付,手机银行就是建行手机银行支付客户端,其实手机、网银,区分不是很严格,一般都设置成一样的,同一笔支付,可能会触发多渠道的同时反馈。所以,反馈机制,在响应的时候,是允许重复的。一般来说,服务器、页面,是写成两个不同的回调处理。或者,加个条件判断,同时允许post和get,也行。写成一个,就不太好判断反馈的来源了。写成两个,再通过日志,能区分反馈的来源。 在这里插入图片描述 支付完成后,出现这个页面,如果用户不点完成的话,直接左上角叉叉掉,服务器反馈有,页面反馈没有 付款这个环节不能携带任何参数显示出来 在这里插入图片描述 我这里页面反馈和服务器反馈写的不同方法,请求方式不同,服务器反馈主要是操作数据库,更新支付成功信息到数据库,页面反馈主要是用来展示成功的页面,订单信息等等展示给用户

/** * 支付回调(页面反馈 get)付款人付款完成后,点击“返回商户网站”按钮,触发页面反馈。 * * @return */ @GetMapping("/payCallBackForPage") @ResponseBody public SuccessVo payCallBackForPage(PayCallBackEntity payCallBackEntity, HttpServletResponse response) throws Exception { System.out.println("payCallBackEntity = " + payCallBackEntity); String success = payCallBackEntity.getSUCCESS(); String orderId = payCallBackEntity.getORDERID(); String payment = payCallBackEntity.getPAYMENT(); System.out.println("success: -" + success); System.out.println("orderId: -" + orderId); if ("Y".equals(success)) { Map map = jinshiCCBPayService.selectByOrderId(orderId); Object returnCode = map.get("returnCode"); Object returnMsg = map.get("returnMsg"); logger.info("returnCode: " + returnCode); logger.info("returnMsg: " + returnMsg); if ("000000".equals(returnCode)) { //返回 "000000" 说明此订单号已支付成功 //写支付成功以后的操作 //todo } } else { //支付失败 } return new SuccessVo(); }

其中用到了根据订单号查询订单的方法,如下:

@Override public Map selectByOrderId(String orderId) { //订单号 如果有了订单号,下面的 ORDERDATE BEGORDERTIME ENDORDERTIME 就无效了..所以就置空了. String ORDERID = orderId; String ORDERDATE = "20200114"; // 因为有ORDERID,所以这个字段无效了,但是不能删 String BEGORDERTIME = "00:00:00";// 因为有ORDERID,所以这个字段无效了,但是不能删 String ENDORDERTIME = "23:59:59";// 因为有ORDERID,所以这个字段无效了,但是不能删 //txcode=410408 /* 流程类型 必输项 0支付流水 1退款流水*/ String TYPE = "0"; /*必输项(当日只有未结算流水可供查询) 0 未结算流水 1 已结算流水*/ String KIND = "1"; /*必输项 0失败 1成功 2不确定 3全部(已结算流水查询不支持全部)*/ String STATUS = "1"; //页码必输项,输入将要查询的页码。 String PAGE = "1"; String xmlString = this.getStringByHttpClient(ORDERDATE, BEGORDERTIME, ENDORDERTIME, ORDERID, TYPE, KIND, STATUS, PAGE); Document document = (Document) this.getDocumentByXMLStr(xmlString); List queryorders = document.getQUERYORDER(); Map resultMap = new HashMap(); if (!CollectionUtils.isEmpty(queryorders)) { // 如果没有查询到定单就是空的 QUERYORDER queryOrder = queryorders.get(0); //因为只有一个结果,所以就获取索引为0的元素 resultMap.put("queryOrder", queryOrder); } //不管查询成功查询失败都会有这个消息 resultMap.put("returnCode", document.getRETURN_CODE()); resultMap.put("returnMsg", document.getRETURN_MSG()); return resultMap; } /** * 发送请求获取 String格式的字符串 * * @param ORDERDATE * @param BEGORDERTIME * @param ENDORDERTIME * @param ORDERID * @param TYPE * @param KIND * @param STATUS * @param PAGE * @return string 格式的xml */ private String getStringByHttpClient(Object ORDERDATE, Object BEGORDERTIME, Object ENDORDERTIME, String ORDERID, String TYPE, String KIND, String STATUS, String PAGE) { String param = "MERCHANTID=" + MERCHANTID + "&BRANCHID=" + BRANCHID + "&POSID=" + POSID + "&ORDERDATE=" + ORDERDATE + "&BEGORDERTIME=" + BEGORDERTIME + "&ENDORDERTIME=" + ENDORDERTIME + "&BEGORDERID=" + BEGORDERID + "&ENDORDERID=" + ENDORDERID + "&QUPWD=&TXCODE=" + TXCODE + "&SEL_TYPE=" + SEL_TYPE + "&OPERATOR=" + OPERATOR; if ("410408".equals(TXCODE)) { param = "MERCHANTID=" + MERCHANTID + "&BRANCHID=" + BRANCHID + "&POSID=" + POSID + "&ORDERDATE=" + ORDERDATE + "&BEGORDERTIME=" + BEGORDERTIME + "&ENDORDERTIME=" + ENDORDERTIME + "&ORDERID=" + ORDERID + "&QUPWD=&TXCODE=" + TXCODE + "&TYPE=" + TYPE + "&KIND=" + KIND + "&STATUS=" + STATUS + "&SEL_TYPE=" + SEL_TYPE + "&PAGE=" + PAGE + "&OPERATOR=" + OPERATOR + "&CHANNEL=" + CHANNEL; } Map map = new HashMap(); map.put("MERCHANTID", MERCHANTID); map.put("BRANCHID", BRANCHID); map.put("POSID", POSID); map.put("ORDERDATE", ORDERDATE); map.put("BEGORDERTIME", BEGORDERTIME); map.put("ENDORDERTIME", ENDORDERTIME); map.put("BEGORDERID", BEGORDERID); map.put("ENDORDERID", ENDORDERID); map.put("QUPWD", QUPWD); map.put("TXCODE", TXCODE); if ("410408".equals(TXCODE)) { map.put("TYPE", TYPE); map.put("KIND", KIND); map.put("STATUS", STATUS); map.put("ORDERID", ORDERID); map.put("PAGE", PAGE); map.put("CHANNEL", CHANNEL); } map.put("SEL_TYPE", SEL_TYPE); map.put("OPERATOR", OPERATOR); map.put("MAC", MD5.md5Str(param)); // 调用银行的接口 基本是固定的地址 String s = HttpClientUtil.httpPost("https://ibsbjstar.ccb.com.cn/CCBIS/ccbMain?", map); //删除字符串防止解析xml报错 s = s.replaceAll("\\n", ""). replaceAll("\\t", ""). replaceAll("\\r", ""); return s; } /** * 从 string 格式的xml 里面提出取出 Document 实体类 * @param ret string 格式的xml * @return */ private Object getDocumentByXMLStr(String ret) { XStream xStream = new XStream(); xStream.alias("DOCUMENT", Document.class); xStream.processAnnotations(Document.class); XStream.setupDefaultSecurity(xStream); xStream.allowTypesByWildcard( new String[]{"com.xxxxx.**"} ); return xStream.fromXML(ret); }

以下的实体类省略getset方法 Document 实体类

import com.thoughtworks.xstream.annotations.XStreamAlias; import com.thoughtworks.xstream.annotations.XStreamImplicit; import java.util.List; @XStreamAlias("DOCUMENT") public class Document { @XStreamImplicit(itemFieldName ="QUERYORDER") private List QUERYORDER ; // 查询订单 private String RETURN_CODE; // 返回状态码 private String RETURN_MSG; // 返回消息 private String CURPAGE; //当前页 private String PAGECOUNT; //总页数 private String TOTAL; //总数 private String PAYAMOUNT; //付款方式 private String REFUNDAMOUNT; // 修正??? }

PayCallBackEntity 实体类

/** * 建行支付回调实体类 */ 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.httpclienthods.GetMethod; import org.apache.commons.httpclienthods.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 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 "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 private String SUCCESS; private String PAYURL; private String QRURL; //安卓点这个会直接跳到支付页面 }

QUERYORDER 实体类

public class QUERYORDER { private String MERCHANTID; //商户代码 private String BRANCHID; //分行代码 private String POSID; //柜台号 private String ORDERID; //订单号 private String ORDERDATE; //订单支付的时间 private String ACCDATE; //访问日期记录 private String AMOUNT; //支付金额 private String STATUSCODE; //状态码 private String STATUS; //交易状态 支付成功的话, 会返回 "成功" private String REFUND; //退税??? private String SIGN; //签名 }


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有