写在前面
最近两天整合对接了工商银行的聚合支付通道,目前已上线运行。踩了一些坑,以此记录供网友分享。
此文对接产品的主要是【线上POS聚合消费下单接口(无界面)】
准备工作
1.开发指南、SDK、API官方文档 https://open.icbc.com.cn/icbc/apip/docs_index.html
2.向工行客服申请并获得 APPID、商户编号、公私钥、应用网关、协议编号等等
3.了解RSA公私钥签名的基本概念,建议阅读-> 点击这里
4.下载SDK,https://open.icbc.com.cn/icbc/apip/docs_sdk&demo.html
开干
1、必须要准备的参数
public class IcbcConfig { /** 聚合支付B2C线上消费下单接口url (无界面) */ public final static String API_PAY_URL = "https://gw.open.icbc.com.cn/api/cardbusiness/aggregatepay/b2c/online/consumepurchase/V1";
/** 退款url */
public final static String REFUND_URL = "https://gw.open.icbc.com.cn/api/cardbusiness/aggregatepay/b2c/online/merrefund/V1";
/** 我方私钥 */ public final static String MY_PRIVATE_KEY = "MIIEvgIBA......"; /** 我方公钥 (可以不用) */ public final static String MY_PUBLIC_KEY = ""; /** 对方(即工行)网关公钥 */ public final static String APIGW_PUBLIC_KEY = "MIGfMA0GCSqGS......"; /** APP的编号,应用在API开放平台注册时生成 eg:10000000000004095781 */ public final static String APP_ID = "10000000000004095781"; /** 商户编号 */ public final static String mer_id = "4402......."; /** 收单产品协议编号 */ public final static String mer_prtcl_no = "44021......";
/** 设备号 (自定义不超过文档规定长度就行) */ public final static String decive_info = "1124........"; }
2.在工程中引入SDK
把需要的jar包导入本地仓库,并在pom.xml引入
mvn install:install-file -DgroupId=com.xxx -DartifactId=xxx-xxx -Dversion=1.x.x -Dpackaging=jar -Dfile=D:\xxx.jar
需要的jar包有三个(后面签名、验签等操作需要用到)
3.请求下单接口
public static void callPay() { try { //注意:这里一般使用的是RSA2签名方式,文档里面默认是RSA DefaultIcbcClient client = new DefaultIcbcClient( IcbcConfig.APP_ID, IcbcConstants.SIGN_TYPE_RSA2, IcbcConfig.MY_PRIVATE_KEY, IcbcConfig.APIGW_PUBLIC_KEY); CardbusinessAggregatepayB2cOnlineConsumepurchaseRequestV1 request = new CardbusinessAggregatepayB2cOnlineConsumepurchaseRequestV1(); request.setServiceUrl(IcbcConfig.API_PAY_URL); CardbusinessAggregatepayB2cOnlineConsumepurchaseRequestV1.CardbusinessAggregatepayB2cOnlineConsumepurchaseRequestV1Biz bizContent = new CardbusinessAggregatepayB2cOnlineConsumepurchaseRequestV1.CardbusinessAggregatepayB2cOnlineConsumepurchaseRequestV1Biz(); request.setBizContent(bizContent); bizContent.setMer_id(IcbcConfig.mer_id); bizContent.setMer_prtcl_no(IcbcConfig.mer_prtcl_no); bizContent.setDecive_info(IcbcConfig.decive_info); bizContent.setOut_trade_no("test123"); //交易日期时间,需要格式化为yyyyMM-dd'T'HH:mm:ss bizContent.setOrig_date_time("2019‐07‐09T12:11:03"); //交易币种,目前工行只支持使用人民币(001)支付 bizContent.setFee_type("001"); bizContent.setSpbill_create_ip("122.12.12.12"); //金额,单位分 bizContent.setTotal_fee("100"); //支付成功后回调url bizContent.setMer_url("http://www.test.com/testNotify123"); bizContent.setBody("测试支付"); //收单接入方式,5-APP,7-微信公众号,8-支付宝生活号,9-微信小程序 bizContent.setAccess_type("9"); //支付方式,9-微信;10-支付宝; bizContent.setPay_mode("9"); //商户在微信开放平台注册的APPID,支付方式为微信时不能为空 bizContent.setShop_appid("wx8888888888888888"); // //第三方用户标识,商户在支付宝生活号接入时必送,即access_type为8时,上送用户的唯一标识;商户通过微信公众号内或微信小程序接入时不送 // bizContent.setUnion_id(""); //第三方用户标识,商户在微信公众号内或微信小程序内接入时必送,即access_type为7或9时,上送用户在商户APPID下的唯一标识;商户通过支付宝生活号接入时不送 bizContent.setOpen_id(""); bizContent.setIcbc_appid(IcbcConfig.APP_ID); // bizContent.setMer_acct("6212880200000038618"); bizContent.setExpire_time("120"); //附加数据,在查询API和支付通知中原样返回,该字段主要用于商户携带订单的自定义数据 bizContent.setAttach(""); //通知类型,表示在交易处理完成后把交易结果通知商户的处理模式。取值“HS”:在交易完成后将通知信息,主动发送给商户,发送地址为notify_url指定地址; 取值“AG”:在交易完成后不通知商户。不送或送空,默认为"HS" bizContent.setNotify_type("HS"); //结果发送类型,通知方式为HS时有效。取值“0”:无论支付成功或者失败,银行都向商户发送交易通知信息;取值“1”,银行只向商户发送交易成功的通知信息。默认是"0" bizContent.setResult_type("0"); //支付方式限定,上送”no_credit“表示不支持信用卡支付;上送“no_balance”表示仅支持银行卡支付;不上送或上送空表示无限制 bizContent.setPay_limit(""); //订单附加信息 bizContent.setOrder_apd_inf(""); CardbusinessAggregatepayB2cOnlineConsumepurchaseResponseV1 response; try { response = client.execute(request); if (response.getReturnCode() == 0) { // 6、业务成功处理,请根据接口文档用response.getxxx()获取同步返回的业务数据 System.out.println("ReturnCode:"+response.getReturnCode()); System.out.println("response:" + JSON.toJSONString(response)); //response.getWx_data_package() 就是微信小程序调起支付所需要的参数 } else { // 失败 System.out.println("response:" + JSON.toJSONString(response)); System.out.println("ReturnCode:"+response.getReturnCode()); System.out.println("ReturnMsg:"+response.getReturnMsg()); } } catch (IcbcApiException e) { log.error(e.toString(), e); } } catch (Exception e) { log.error(e.toString(), e); } }
4.支付回调及验签
private static Logger log = Log.get(); @PostMapping("/icbcWxNotify") @ResponseBody public void icbcWxNotify(HttpServletRequest request, HttpServletResponse response){ PrintWriter out = null; try { //签名是否验证成功 boolean signResult = false; //响应参数 JSONObject bizContent = JSON.parseObject(request.getParameter("biz_content")); //我们的订单编号 String orderCode = bizContent.getString("out_trade_no"); //订单金额(单位分) String totalFee = bizContent.getString("total_amt"); //第三方订单流水号 String transactionId = bizContent.getString("third_trade_no"); //消息号 String msgId = bizContent.getString("msg_id"); // 1.验证签名 // 注意:当notify_url=http://122.20.29.133:8080/testNotify123时,notifyUrl=testNotify123 String notifyUrl = "testNotify123"; if (checkSign(request, notifyUrl)) { signResult = true; log.info("订单{}【工商银行】微信回调签名认证成功", orderCode); // 2.执行我们的业务逻辑处理 //executeLogic(orderCode, totalFee, transactionId); } else { log.error("订单{}【工商银行】微信支付回调签名认值失败!", orderCode); } // 3.应答工行:在接收到工行的支付结果通知后,一定要返回应答,否则工行会认为该通知失败,在一定时间区间内多次发起通知 String results = notifyStr(signResult, msgId); response.setContentType("application/json; charset=utf-8"); out = response.getWriter(); out.write(results); } catch (Exception e) { log.error("【工商银行】微信支付回调处理失败,请检查原因!!!,{}", e.getMessage()); } finally { out.flush(); out.close(); } } /** * 验证签名 * @param request 回调请求 * @param path 我方回调url * @return true:验证成功 false:验证失败 */ public static boolean checkSign(HttpServletRequest request, String path) { Map<String, String> params = Maps.newHashMap(); String from = request.getParameter("from"); String api = request.getParameter("api"); String app_id = request.getParameter("app_id"); String charset = request.getParameter("charset"); String format = request.getParameter("format"); String timestamp = request.getParameter("timestamp"); String biz_content = request.getParameter("biz_content"); String sign_type = request.getParameter("sign_type"); String sign = request.getParameter("sign"); params.put("from", from); params.put("api", api); params.put("app_id", app_id); params.put("charset", charset); params.put("format", format); params.put("timestamp", timestamp); params.put("biz_content", biz_content); //目前上行网关签名暂时仅支持RSA params.put("sign_type", sign_type); String signStr= WebUtils.buildOrderedSignStr(path, params); try { //注意:此次用的是RSA,和下单时不一样 return IcbcSignature.verify(signStr, IcbcConstants.SIGN_TYPE_RSA, IcbcConfig.APIGW_PUBLIC_KEY, charset, sign); } catch (IcbcApiException e) { return false; } } /** * 支付回调应答字符串 * @param checkResult 回调后签名是否校验成功 * @param msgId 消息号 * @return str */ public static String notifyStr(boolean checkResult, String msgId) { // return_code为数字,成功时为0 String responseBizContent; if (checkResult) { responseBizContent = "{\"return_code\":0,\"return_msg\":\"success\",\"msg_id\":\"" + msgId + "\"}"; } else { responseBizContent = "{\"return_code\":-12345,\"return_msg\":\"icbc sign not pass.\"}"; } // sign_type-商户在工行登记app的签名类型保持一致,一般为RSA2 // 返回字符串顺序不能变,为response_biz_content、sign_type、sign,中间不含空格换行符; String signStr = "\"response_biz_content\":" + responseBizContent + "," + "\"sign_type\":\"RSA2\""; String sign = null; try { sign = IcbcSignature.sign(signStr, IcbcConstants.SIGN_TYPE_RSA2, IcbcConfig.MY_PRIVATE_KEY,"UTF-8",""); } catch (IcbcApiException e) { log.error("【工商银行】微信回调-应答签名失败", e); } return "{" + signStr + ",\"sign\":\"" + sign + "\"}"; }
5.退款
/** * 退款 * @param orderCode 订单编号 * @param refundCode 退款编号 * @param refundMoney 部分退款金额 * @return true:退款成功 false:退款失败 */ public static boolean refund(String orderCode, String refundCode, BigDecimal refundMoney) { DefaultIcbcClient client = new DefaultIcbcClient( IcbcConfig.APP_ID, IcbcConstants.SIGN_TYPE_RSA2, IcbcConfig.MY_PRIVATE_KEY, IcbcConfig.APIGW_PUBLIC_KEY); CardbusinessAggregatepayB2cOnlineMerrefundRequestV1 request = new CardbusinessAggregatepayB2cOnlineMerrefundRequestV1(); //根据测试环境和生产环境替换相应ip和端口 request.setServiceUrl(IcbcConfig.REFUND_URL); //请对照接口文档用bizContent.setxxx()方法对业务上送数据进行赋值 CardbusinessAggregatepayB2cOnlineMerrefundRequestV1.CardbusinessAggregatepayB2cOnlineMerrefundRequestV1Biz bizContent = new CardbusinessAggregatepayB2cOnlineMerrefundRequestV1.CardbusinessAggregatepayB2cOnlineMerrefundRequestV1Biz(); request.setBizContent(bizContent); //工行订单号,工行订单号,商户订单号或行内订单号必须其中一个不为空 bizContent.setOrder_id(""); //商户编号‐必输项 bizContent.setMer_id(IcbcConfig.mer_id); //商户订单号,工行订单号,商户订单号或行内订单号必须其中一个不为空 bizContent.setOut_trade_no(orderCode); //退货流水号,商户系统生成的退款编号,每次部分退款需生成不同的退款编号 bizContent.setOuttrx_serial_no(refundCode); //退货总金额‐必输项 BigDecimal realRefundMoney = MathUtil.multiply(refundMoney, 100, 0); bizContent.setRet_total_amt(realRefundMoney.toString()); //交易币种‐必输项 bizContent.setTrnsc_ccy("001"); bizContent.setOrder_apd_inf(""); bizContent.setIcbc_appid(IcbcConfig.APP_ID); bizContent.setMer_acct(""); CardbusinessAggregatepayB2cOnlineMerrefundResponseV1 response; try { response = client.execute(request); if (response.isSuccess()) { // 业务成功处理,请根据接口文档用response.getxxx()获取同步返回的业务数据 // System.out.println("ReturnCode:"+response.getReturnCode()); // System.out.println("response:" + response); return true; } else { // 失败 // System.out.println("ReturnCode:"+response.getReturnCode()); // System.out.println("ReturnMsg:"+response.getReturnMsg()); } } catch (IcbcApiException e) { log.error(e.toString(), e); } return false; }
总结
1.工行的支付通道其实就是他们自己封装了一下支付参数,原理上还是调用微信的接口
2.支付的订单流水记录不在我们自己的微信商户后台,在工商银行那边,查询记录要在他们的系统上面查询,比较麻烦,最好在自己的系统上区分一下工商银行通道的支付和官方微信支付
3.最好使用他们的SDK来进行签名和验签等操作,避免不必要的麻烦
4.如果使用APP-微信支付,流程是APP调起自己的微信小程序,再使用微信小程序唤起收银台,并且微信小程序调起支付必须要有openid,还多了一个中转的过程,需要权限利弊
标签:Java,String,微信,bizContent,request,IcbcConfig,支付,response From: https://www.cnblogs.com/xhq1024/p/16963655.html