最近公司的项目接了个建行生活生活支付,文档诟病良多,把一些坑记录一下防止大家踩坑。
一、首先需要接入授权登录,需要服务方公私钥加解密建行生活跳转带过来的参数,光这个就浪费了两天时间,建行的工作人员提供各种钥匙无果,我就是各种解密失败,直到第三天才提供了正确的服务方公私钥,然后第一个坑就来了,这是建行官方文档提供的解密方法:
/**
* 解密及验签
*/
// base64逆处理并用私钥解密
BASE64Decoder decoder = new BASE64Decoder();
enc_msg = new String(decoder.decodeBuffer(enc_msg),"UTF-8");
String dec_msg = RSAUtil.decrypt(enc_msg, privateKey);
但是文档上根本没有写在base64解密之前必须要先解码,这个又浪费了两个小时,要先加上
enc_msg = new String(URLDecoder.decode(enc_msg));
二、接入完登录就要接支付了,这里来第二个坑是自己踩得,这个坑浪费了几次三天时间,面壁思过…就是拉起支付的时候页面上加了JSON.Stringfy(paramStr),死活拉起不了支付,最后在别人的提醒下才解决
这里还有一个要注意的点,前面授权登录解密ccbParamSj 的时候需要使用privateKey私钥解密,到这里传参加密MAC的时候不需要加privateKey,只用 MD5Util.getMD5这个方法就可以了
三、接好支付后配置回调地址需要配置的是第二个反馈地址,并且这个地址有半个小时左右的延迟,配置了不会立刻生效,不知道是建行这样还是所有银行都这样的,不多试几次很难知道
四、支付回调验签的时候又是一个大坑,拉起支付的时候无论是否传了clientip参数,回调通知的时候就是不给你,诶,气不气,关键支付回调验签文档里这个参数又是必须参加验签的,却没地儿拿值
然后试了好多次才发现不传才能通过验签,不然就是过不了,真是大无语了
五、正式上线后是从首页banner点进去,建行的运营人员不熟悉业务,配置链接的时候没有勾选授权登录,新用户根本无法获取用户信息,得,又浪费我几个小时调试时间才把问题拋回去
最后放一下公用的代码把,这东西开发人员不能放一套示例代码到材料文档里吗,重复造轮子没有意思,放一下节约时间,要用的直接复制就行了
授权登录
// 解码
userCode = new String(URLDecoder.decode(userCode));
// base64逆处理并用私钥解密
BASE64Decoder decoder = new BASE64Decoder();
userCode = new String(decoder.decodeBuffer(userCode),"UTF-8");
String dec_msg = RSAUtil.decrypt(userCode, privateKey);
String arr[] = dec_msg.split("&");
String userId = null;
String mobile = null;
String timeStamp = null;
for(String str :arr){
if("USERID".equals(str.split("=")[0])){
userId = str.split("=")[1];
}
if("MOBILE".equals(str.split("=")[0])){
mobile = str.split("=")[1];
}
if("TIMESTAMP".equals("")){
timeStamp = str.split("=")[1];
}
}
logger.info("accessToken:{},获取用户信息返回报文:{}", dec_msg);
拉起支付报文
String MERCHANTID ="商户代码"; // 商户代码
String POSID = "柜台代码"; // 柜台代码
String BRANCHID = "分行代码"; // 分行代码
String ORDERID = orderSn; // 订单号
String PAYMENT = realPay; // 付款金额
String CURCODE = "01"; // 币种 缺省为01-人民币(只支持人民币支付)
String TXCODE = "520100"; // 交易码
String REMARK1 = "ccbpay"; // 备注1
String REMARK2 = serviceNo; // 备注2 上送YS开头的服务方编号
String TYPE = "1"; // 接口类型 默认送1 - 防钓鱼接口
String GATEWAY = "0"; // 网关类型 默认送0
String CLIENTIP = ""; // 客户端IP 客户在商户系统中的IP,送空值即可
String REGINFO = ""; // 客户注册信息
String PROINFO = ""; //
String REFERER = ""; // 商户URL 商户送空值即可
String THIRDAPPINFO = "comccbpay1234567890cloudmerchant"; // 客户端标识
String TIMEOUT =cn.hutool.core.date.DateUtil.format(cn.hutool.core.date.DateUtil.offsetHour(new Date(), 1), DatePattern.PURE_DATETIME_PATTERN); // 订单超时时间 YYYYMMDDHHMMSS
String PLATFORMPUB = publicKey; // 服务方公钥 仅作为源串参加MD5摘要,不作为参数传递
String MAC = ""; // MD5加密串 采用标准MD5算法,对以上字段进行MAC加密(32位小写)
String PLATFORMID = serviceNo; // 服务方编号 仅作为参数传递,不参与MAC校验
String ENCPUB = ""; // 商户公钥密文 仅作为参数传递,不参与MAC校验
StringBuffer macBuffer = new StringBuffer();
macBuffer.append("MERCHANTID="+MERCHANTID);
macBuffer.append("&POSID="+POSID);
macBuffer.append("&BRANCHID="+BRANCHID);
macBuffer.append("&ORDERID="+ORDERID);
macBuffer.append("&PAYMENT="+PAYMENT);
macBuffer.append("&CURCODE="+CURCODE);
macBuffer.append("&TXCODE="+TXCODE);
macBuffer.append("&REMARK1="+REMARK1);
macBuffer.append("&REMARK2="+REMARK2);
macBuffer.append("&TYPE="+TYPE);
macBuffer.append("&GATEWAY="+GATEWAY);
macBuffer.append("&CLIENTIP="+CLIENTIP);
macBuffer.append("®INFO="+REGINFO);
macBuffer.append("&PROINFO="+PROINFO);
macBuffer.append("&REFERER="+REFERER);
macBuffer.append("&THIRDAPPINFO="+THIRDAPPINFO);
macBuffer.append("&TIMEOUT="+TIMEOUT);
macBuffer.append("&PLATFORMPUB="+PLATFORMPUB);
MAC = MD5Util.getMD5(macBuffer.toString()).toLowerCase();
StringBuffer paramBuffer = new StringBuffer();
paramBuffer.append("MERCHANTID="+MERCHANTID);
paramBuffer.append("&POSID="+POSID);
paramBuffer.append("&BRANCHID="+BRANCHID);
paramBuffer.append("&ORDERID="+ORDERID);
paramBuffer.append("&PAYMENT="+PAYMENT);
paramBuffer.append("&CURCODE="+CURCODE);
paramBuffer.append("&TXCODE="+TXCODE);
paramBuffer.append("&REMARK1="+REMARK1);
paramBuffer.append("&REMARK2="+REMARK2);
paramBuffer.append("&TYPE="+TYPE);
paramBuffer.append("&GATEWAY="+GATEWAY);
paramBuffer.append("&CLIENTIP="+CLIENTIP);
paramBuffer.append("®INFO="+REGINFO);
paramBuffer.append("&PROINFO="+PROINFO);
paramBuffer.append("&REFERER="+REFERER);
paramBuffer.append("&THIRDAPPINFO="+THIRDAPPINFO);
paramBuffer.append("&TIMEOUT="+TIMEOUT);
paramBuffer.append("&MAC="+MAC);
paramBuffer.append("&PLATFORMID="+PLATFORMID);
String enc_msg = RSAUtil.encrypt(merPublicKeyStr, publicKey);
BASE64Encoder encoder = new BASE64Encoder();
enc_msg = encoder.encode(enc_msg.getBytes("UTF-8"));
enc_msg = enc_msg.replaceAll("\r\n", "").replaceAll("\r", "").replaceAll("\n", "");
ENCPUB = enc_msg;
paramBuffer.append("&ENCPUB="+ENCPUB);
支付通知回调
@Transactional(rollbackFor = Exception.class)
public void payCallback(Map<String, String> reqParam, HttpServletResponse resp) throws Exception {
String SUCCESS = reqParam.get("SUCCESS");
try {
if ("Y".equals(SUCCESS)) {
logger.info("建行生活支付通知-1-[支付]-回调成功");
// 必填字段
String POSID = reqParam.get("POSID");
String BRANCHID = reqParam.get("BRANCHID");
String ORDERID = reqParam.get("ORDERID");
String PAYMENT = reqParam.get("PAYMENT");
String CURCODE = reqParam.get("CURCODE");
String REMARK1 = reqParam.get("REMARK1");
String REMARK2 = reqParam.get("REMARK2");
String ACC_TYPE = reqParam.get("ACC_TYPE");
// String CLIENTIP = reqParam.get("CLIENTIP");
String SIGN = reqParam.get("SIGN");
// 优惠
String CCB_DISCOUNT_AMT = reqParam.get("CCB_DISCOUNT_AMT");
String CCB_DISCOUNT_AMT_DESC = reqParam.get("CCB_DISCOUNT_AMT_DESC");
StringBuilder signBuilder = new StringBuilder();
signBuilder.append("POSID=" + POSID);
signBuilder.append("&BRANCHID=" + BRANCHID);
signBuilder.append("&ORDERID=" + ORDERID);
signBuilder.append("&PAYMENT=" + PAYMENT);
signBuilder.append("&CURCODE=" + CURCODE);
signBuilder.append("&REMARK1=" + REMARK1);
signBuilder.append("&REMARK2=" + REMARK2);
signBuilder.append("&ACC_TYPE=" + ACC_TYPE);
signBuilder.append("&SUCCESS=" + SUCCESS);
// signBuilder.append("&CLIENTIP="+CLIENTIP); // 这个不能加才能验签通过 是个坑
LsOrder order = lsOrderService.getByOrderSn(ORDERID);
// 订单实付价格
BigDecimal realPrice = new BigDecimal(order.getRealPrice());
BigDecimal divide = realPrice.divide(new BigDecimal("100"));
DecimalFormat decimalFormat = new DecimalFormat("0.00");
String realPay = decimalFormat.format(divide.doubleValue());
RSASig rsaSig = new RSASig();
rsaSig.setPublicKey(merPublicKey);
boolean res = rsaSig.verifySigature(SIGN, signBuilder.toString());
if (res) {
logger.info("建行生活支付通知-2-[签名]-校验成功");
// 无优惠券
if(StringUtils.isBlank(CCB_DISCOUNT_AMT)){
if (realPay.equals(PAYMENT)) {
logger.info("建行生活支付通知-3-[金额]-校验成功");
callBackHandler(ORDERID);
} else {
logger.error("建行生活支付通知-3-[金额]-校验失败");
}
}else {
// 优惠金额
BASE64Decoder decoder = new BASE64Decoder();
CCB_DISCOUNT_AMT = RSAUtil.decrypt(new String(decoder.decodeBuffer(CCB_DISCOUNT_AMT), "UTF-8"), privateKey);
// 商户获取金额 = 建行实付 + 优惠金额
BigDecimal getMoney = new BigDecimal(PAYMENT).add(new BigDecimal(CCB_DISCOUNT_AMT));
String getMoneyStr = decimalFormat.format(getMoney.doubleValue());
if(realPay.equals(getMoneyStr)){
logger.info("建行生活支付通知-3-[金额]-校验成功-[使用优惠券]");
callBackHandler(ORDERID);
}else{
logger.info("建行生活支付通知-3-[金额]-校验失败-[使用优惠券]");
}
}
} else {
logger.error("建行生活支付通知-2-[签名]-校验失败");
}
} else {
logger.error("建行生活支付通知-1-[支付失败]");
}
}catch (Exception e){
logger.error("建行生活app-[支付通知回调接口异常]:{}", ExceptionUtils.getStackTrace(e));
}
}
其他的订单状态更新、退款都差不多,就不赘述了