Java对接工商银行聚合支付(无界面)(微信小程序支付&回调验签&退款)

写在前面

最近两天整合对接了工商银行的聚合支付通道,目前已上线运行。踩了一些坑,以此记录供网友分享。

此文对接产品的主要是【线上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.支付回调及验签

    @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 returnCode = bizContent.getString("return_code");
//
消息号 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);
          if ("0".equals(returnCode)) {
            // 返回码,交易成功返回0,其他表示业务报错 注意:调起支付未支付也会回调,所以此处必须判断return_code=0   
// 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\":-1,\"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,还多了一个中转的过程,需要权限利弊

posted @ 2022-12-08 11:46  猿了个码  阅读(4348)  评论(7编辑  收藏  举报