最近项目在做融360引流,涉及到了易宝支付的代扣和代付。易宝官方给出的demo只能简单运行,而且都是通过form表单的形式提交,返回XML格式。同时接口代码都写在了JSP中看起来不友好。项目在生成中想要用,必须修改整合到自己的项目工程中(文末附我修改的源码下载地址)。
首先说明易宝的版本号:易宝支付-代付代发-商户接入包-V3.3
言归正传,相信易宝官方的demo大家都能获取到,所以这里不再赘述demo的难看点。在这里直接截图给大家说明我修改后的代码。有不好的地方请大家看了代码后及时在下方评论
上图是通过request获取项目的真实路径,以便下面用于获取证书地址,但是实际我们在封装成自己的代码时,传入request是不太好的,因为有可能在我们用的时候,无法获取到request对象。而且这里的功能单一,所以我用下面的方法进行了修改,以便获取到证书真实路径(urlss这里需要替换成自己的当前类全限定名称,TransferParamResolver这个都要替换。下面有可Cp代码)
这里代替为用静态代码块获取证书的真实路径,这里的System.Properties功能还是很多的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | // System.err.println(sysPath); // Properties props = System.getProperties(); // propertis = props.getProperty("user.dir")+"文件分隔符:" + //props.getProperty("file.separator"); // System.out.println("操作系统的名称:" + props.getProperty("os.name")); // System.out.println("操作系统的构架:" + props.getProperty("os.arch")); // System.out.println("操作系统的版本号:" + props.getProperty("os.version")); // System.out.println("文件分隔符:" + props.getProperty("file.separator")); // //在 unix 系统中是"/" // System.out.println("路径分隔符:" + props.getProperty("path.separator")); // //在 unix 系统中是":" // System.out.println("行分隔符:" + props.getProperty("line.separator")); // //在 unix 系统中是"/n" // System.out.println("用户的账户名称:" + props.getProperty("user.name")); // System.out.println("用户的主文件夹:" + props.getProperty("user.home")); // System.out.println("用户的当前工作文件夹:" + props.getProperty("user.dir")); // 操作系统的名称:Windows 8.1 // 操作系统的构架:amd64 // 操作系统的版本号:6.3 // 文件分隔符: // 路径分隔符:; // 行分隔符: // 用户的账户名称:雷神 // 用户的主文件夹:C:Users雷神 // 用户的当前工作文件夹:D:JAVAInstallintelIdeaWorkSpaceTestProject1 |
剩下的就是传参和返回值的获取,这里直接放入所有代码
package com.ssm.yibaoPay.yeepay.common.transfer; import com.cfca.util.pki.api.CertUtil; import com.cfca.util.pki.api.KeyUtil; import com.cfca.util.pki.api.SignatureUtil; import com.cfca.util.pki.cert.X509Cert; import com.cfca.util.pki.cipher.JCrypto; import com.cfca.util.pki.cipher.JKey; import com.cfca.util.pki.cipher.Session; import com.ssm.yibaoPay.yeepay.common.securityplatform.Digest; import com.ssm.yibaoPay.yeepay.common.utils.CallbackUtils; import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.DocumentHelper; import org.dom4j.Element; import java.io.File; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; /** * com.fastx.cooperate.threeInterface.yeepay.common.transfer; * 功能:; * * @author 李冉 Email:[email protected] * Time:2018/7/11 10:56 */ public class TransferParamResolver { private static String sysPath; static { String urlss = "com.fastx.cooperate.threeInterface.yeepay.common.transfer.TransferParamResolver"; urlss = "classes/" + urlss.substring(0, urlss.indexOf("TransferParamResolver")).replace(".", "/"); java.net.URL url = TransferParamResolver.class.getResource(""); sysPath = (System.getProperties().getProperty("os.name").toUpperCase().startsWith("WIN") ? "" : System.getProperties().getProperty("file.separator")) + String.valueOf(url).substring(String.valueOf(url).indexOf("file:/") + 6, String.valueOf(url).indexOf(urlss)) + "lib/"; } //protected final static Log log = Log.getLog(TransferParamResolver.class); //添加日志 /** * 单笔打款xml请求报文 * * @param transferSingle * @return * @throws Exception */ public static Map transferSingle(TransferSingle transferSingle) throws Exception {
//TranferSingle 这个实体类 是传参的实体,对应文档自己查看,生产中把下面amount对应修改成自己的实际金额即可 String xml = "<data> " + " <cmd>TransferSingle</cmd> " + " <version>1.1</version> " + " <mer_Id>10022581269</mer_Id> " + " <group_Id>10022581269</group_Id> " + " <batch_No>" + transferSingle.getBatch_no() + "</batch_No> " + " <order_Id>" + transferSingle.getOrder_id() + "</order_Id> " + " <bank_Code>" + transferSingle.getBank_code() + "</bank_Code> " + " <cnaps>100123123123</cnaps> " + " <bank_Name>" + transferSingle.getBank_name() + "</bank_Name> " + " <branch_Bank_Name>农业银行北京市朝阳支行</branch_Bank_Name> " + " <amount>" + "0.01" + "</amount> " + " <account_Name>" + transferSingle.getAccount_name() + "</account_Name> " + " <account_Number>" + transferSingle.getAccount_number() + "</account_Number> " + " <province>110000</province> " + " <city>110000</city> " + " <fee_Type>SOURCE</fee_Type> " + " <payee_Email></payee_Email> " + " <payee_Mobile></payee_Mobile> " + " <leave_Word></leave_Word> " + " <abstractInfo></abstractInfo> " + " <remarksInfo></remarksInfo> " + " <urgency>0</urgency> " + " <hmac></hmac> " + "</data>"; //需要参加签名的参数:其中(hmacKey)指的是商户自己的密钥 String todigestValues = "cmd,mer_Id,batch_No,order_Id,amount,account_Number,hmacKey"; //验证返回参数签名的参数:其中(hmacKey)指的是商户自己的密钥 String tobackDigestValues = "cmd,ret_Code,mer_Id,batch_No,total_Amt,total_Num,r1_Code,hmacKey"; try { return DoTransferSingle(xml, todigestValues, tobackDigestValues); } catch (Exception e) { e.printStackTrace(); // log.info("单笔打款异常---------------->" + e.getMessage()); // log.info("异常输入的参数---------------->" + transferSingle.toString()); throw e; } } /** * 打款明细查询xml请求报文 * * @param betchNo 订单批次号 * @param orderId 订单ID号 * @return * @throws Exception */ public static Map singlePayQuery(String betchNo, String orderId) throws Exception { String xml = "<data> " + " <cmd>BatchDetailQuery</cmd> " + " <version>1.1</version> " + " <group_Id>10022581269</group_Id> " + " <mer_Id>10022581269</mer_Id> " + " <query_Mode>1</query_Mode> " + " <batch_No>" + betchNo + "</batch_No> " + " <order_Id>" + orderId + "</order_Id> " + " <page_No>1</page_No> " + " <hmac></hmac> " + "</data>"; //需要参加签名的参数:其中(hmacKey)指的是商户自己的密钥 String todigestValues = "cmd,mer_Id,batch_No,order_Id,page_No,hmacKey"; //验证返回参数签名的参数:其中(hmacKey)指的是商户自己的密钥 String tobackDigestValues = "cmd,ret_Code,batch_No,total_Num,end_Flag,hmacKey"; try { return DoTransferSingle(xml, todigestValues, tobackDigestValues); } catch (Exception e) { e.printStackTrace(); //log.info("单笔打款查询异常---------------->" + e.getMessage()); // log.info("异常输入的参数---------------->" + "betchNo" + betchNo + "orderId" + orderId); throw e; } } private static Map DoTransferSingle(String xml, String todigestValues, String tobackDigestValues) throws Exception { //商户密钥 String hmacKey = "02Ji5At46r9BwZ8TVW7aFHox1pNm9N0n8c7DsA8e5813IEu74T50i901F762"; Map result = new LinkedHashMap(); Map xmlMap = new LinkedHashMap(); Map xmlBackMap = new LinkedHashMap(); //需要参加签名的参数:其中(hmacKey)指的是商户自己的密钥 String[] digestValues = todigestValues.split(","); //验证返回参数签名的参数:其中(hmacKey)指的是商户自己的密钥 String[] backDigestValues = tobackDigestValues.split(","); //String xml = request.getParameter("xml"); //第一步:将请求的数据和商户自己的密钥拼成一个字符串, Document document = null; try { document = DocumentHelper.parseText(xml); } catch (DocumentException e) { } Element rootEle = document.getRootElement(); String cmdValue = rootEle.elementText("cmd"); List list = rootEle.elements(); for (int i = 0; i < list.size(); i++) { Element ele = (Element) list.get(i); String eleName = ele.getName(); if (!eleName.equals("list")) { xmlMap.put(eleName, ele.getText().trim()); } else { continue; } } String hmacStr = ""; for (int i = 0; i < digestValues.length; i++) { if (digestValues[i].equals("hmacKey")) { hmacStr = hmacStr + hmacKey; continue; } hmacStr = hmacStr + xmlMap.get(digestValues[i]); } System.out.println("签名之前的源数据为---||" + hmacStr + "||"); //下面用数字证书进行签名 Session tempsession = null; String ALGORITHM = SignatureUtil.SHA1_RSA; JCrypto jcrypto = null; if (tempsession == null) { try { //初始化加密库,获得会话session //多线程的应用可以共享一个session,不需要重复,只需初始化一次 //初始化加密库并获得session。 //系统退出后要jcrypto.finalize(),释放加密库 jcrypto = JCrypto.getInstance(); jcrypto.initialize(JCrypto.JSOFT_LIB, null); tempsession = jcrypto.openSession(JCrypto.JSOFT_LIB); } catch (Exception ex) { System.out.println(ex.toString()); } } String sysPath = TransferParamResolver.sysPath; System.out.println("------" + sysPath + "------" + File.separator + "------"); JKey jkey = KeyUtil.getPriKey(sysPath + File.separator + "7.3.pfx", "123456"); X509Cert cert = CertUtil.getCert(sysPath + File.separator + "7.3.pfx", "123456"); System.out.println(cert.getSubject()); X509Cert[] cs = new X509Cert[1]; cs[0] = cert; String signMessage = ""; SignatureUtil signUtil = null; try { // 第二步:对请求的串进行MD5对数据进行签名 String yphs = Digest.hmacSign(hmacStr); signUtil = new SignatureUtil(); byte[] b64SignData; // 第三步:对MD5签名之后数据调用CFCA提供的api方法用商户自己的数字证书进行签名 b64SignData = signUtil.p7SignMessage(true, yphs.getBytes(), ALGORITHM, jkey, cs, tempsession); if (jcrypto != null) { jcrypto.finalize(JCrypto.JSOFT_LIB, null); } signMessage = new String(b64SignData, "UTF-8"); } catch (Exception e) { } System.out.println("经过md5和数字证书签名之后的数据为---||" + signMessage + "||"); Element r = rootEle.element("hmac"); r.setText(signMessage); result.put("xml", xml); document.setXMLEncoding("GBK"); System.out.println("完整xml请求报文:" + document.asXML()); String textHost = "http://cha.yeepay.com/app-merchant-proxy/groupTransferController.action"; System.out.println("请求地址为:" + textHost); //第四步:发送https请求 String responseMsg = CallbackUtils.httpRequest(textHost, document.asXML(), "POST", "gbk", "text/xml ;charset=gbk", false); // out.println( // "<html><body><textarea rows="23" cols="120" name="xml" id="xml">" + // responseMsg + // "</textarea></body></html>"); // System.out.println("服务器响应xml报文:" + responseMsg); try { document = DocumentHelper.parseText(responseMsg); } catch (DocumentException e) { } rootEle = document.getRootElement(); cmdValue = rootEle.elementText("hmac"); //第五步:对服务器响应报文进行验证签名 boolean sigerCertFlag = false; if (cmdValue != null) { sigerCertFlag = signUtil.p7VerifySignMessage(cmdValue.getBytes(), tempsession); String backmd5hmac = xmlBackMap.get("hmac") + ""; if (sigerCertFlag) { System.out.println("证书验签成功"); backmd5hmac = new String(signUtil.getSignedContent()); System.out.println("证书验签获得的MD5签名数据为----" + backmd5hmac); System.out.println("证书验签获得的证书dn为----" + new String(signUtil.getSigerCert()[0].getSubject())); //第六步.将验签出来的结果数据与自己针对响应数据做MD5签名之后的数据进行比较是否相等 Document backDocument = null; try { backDocument = DocumentHelper.parseText(responseMsg); } catch (DocumentException e) { System.out.println(e); } Element backRootEle = backDocument.getRootElement(); List backlist = backRootEle.elements(); for (int i = 0; i < backlist.size(); i++) { Element ele = (Element) backlist.get(i); String eleName = ele.getName(); if (!eleName.equals("list")) { xmlBackMap.put(eleName, ele.getText().trim()); } else { continue; } } String backHmacStr = ""; for (int i = 0; i < backDigestValues.length; i++) { if (backDigestValues[i].equals("hmacKey")) { backHmacStr = backHmacStr + hmacKey; continue; } String tempStr = (String) xmlBackMap.get(backDigestValues[i]); backHmacStr = backHmacStr + ((tempStr == null) ? "" : tempStr); } String newmd5hmac = Digest.hmacSign(backHmacStr); System.out.println("提交返回源数据为---||" + backHmacStr + "||"); System.out.println("经过md5签名后的验证返回hmac为---||" + newmd5hmac + "||"); System.out.println("提交返回的hmac为---||" + backmd5hmac + "||"); if (newmd5hmac.equals(backmd5hmac)) { System.out.println("md5验签成功"); //第七步:判断该证书DN是否为易宝 if (signUtil.getSigerCert()[0].getSubject().toUpperCase().indexOf("OU=YEEPAY,") > 0) { System.out.println("证书DN是易宝的"); if (todigestValues.equals("cmd,mer_Id,batch_No,order_Id,page_No,hmacKey")) { return resolve(responseMsg); } return xmlBackMap; } else { System.out.println("证书DN不是易宝的"); } // } else { System.out.println("md5验签失败"); } } else { System.out.println("证书验签失败...."); } } return null; } private static Map resolve(String responseMsg) { Map xmlBackMap = new LinkedHashMap(); Map listBackMap = new LinkedHashMap(); Document backDocument; try { backDocument = DocumentHelper.parseText(responseMsg); Element backRootEle = backDocument.getRootElement(); List backlist = backRootEle.elements(); for (int i = 0; i < backlist.size(); i++) { Element ele = (Element) backlist.get(i); String eleName = ele.getName(); if (!eleName.equals("list")) { xmlBackMap.put(eleName, ele.getText().trim()); } else { List backList = ((Element) (((Element) (((Element) backlist.get(i)).elements().get(0))).elements().get(0))).elements(); for (int j = 0; j < backList.size(); j++) { Element e1111 = (Element) backList.get(j); listBackMap.put(e1111.getName(), e1111.getText().trim()); } } } xmlBackMap.put("listBackMap", listBackMap); return xmlBackMap; } catch (DocumentException e) { //log.info("易宝解析返回XML参数错误----->" + e.getMessage()); return null; } } /** * 根据易宝代付订单批次号和订单号查询支付状态 * * @param betch_no * @param order_Id * @return */ public static Integer getPayStatus(String betch_no, String order_Id) { //代付状态/1 代付中 /2 代付成功/ 3 代付失败 try { Map transferMap = TransferParamResolver.singlePayQuery(betch_no, order_Id); if (transferMap == null || transferMap.get("listBackMap") == null) { return 3; } Map listBackMap = (Map) transferMap.get("listBackMap"); if (transferMap.get("ret_Code") != null && listBackMap.get("r1_Code") != null && listBackMap.get("bank_Status") != null) { if ("1".equals(String.valueOf(transferMap.get("ret_Code"))) && "0026".equals(String.valueOf(listBackMap.get("r1_Code"))) && "S".equals(String.valueOf(listBackMap.get("bank_Status")))) { return 2; } //失败情况 if ("1".equals(String.valueOf(transferMap.get("ret_Code"))) && ("0026".equals(String.valueOf(listBackMap.get("r1_Code"))) || "0027".equals(String.valueOf(listBackMap.get("r1_Code")))) && "F".equals(String.valueOf(listBackMap.get("bank_Status")))) { return 3; } //正在进行中 if ("1".equals(String.valueOf(transferMap.get("ret_Code"))) && ("0026".equals(String.valueOf(listBackMap.get("r1_Code")))) || "0025".equals(String.valueOf(listBackMap.get("r1_Code")))) { return 1; } } else { return 3; } } catch (Exception e) { e.printStackTrace(); //log部分 } return 3; } }
这里附上源码下载地址(源码在TestProject中):链接:https://pan.baidu.com/s/1UVUCG4zNYDJmJl9q5twrkw 密码:32gw