文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

1.Java接入银联支付(chinapay)教程及避坑点

2023-09-25 16:08

关注

一、背景及效果展示

现如今,诸多的供应链系统需要使用电子钱包功能,所以接入银联B2B无卡支付,是很多系统应用需要做的事情。银联支付的类型分很多种:网关支付(带token请求实现,下次有空再分享)、B2B无卡支付(带证书秘钥请求实现)分商户和机构入网,具体和更详细的内容可以去看中国银联开放平台,今天这里主要针对于企业中所涉及的的B2B无卡快捷支付做介绍与讲解。

二、B2B快捷支付流程

在这里插入图片描述

此流程图在商户接入手册中有展示,可以在文档中详细查看

2.1 B2B支付流程介绍

在这里插入图片描述

我们正常做的B2B支付对于银联来说,即是前台支付(B2B业务中都是前台支付),后台支付不用管。

整体流程走向:

通过后端先将支付请求的参数构建好并返回给到前端,前端请求银联的前台支付地址;

前端处理银联的响应信息(银联返回的是页面,可新开页签),后续到支付流程都是银联侧;
在这里插入图片描述

**注:**此处没有进行银行的选择,是在进行支付请求时传递了银行的机构码,银联的网关前置功能会自动跳转至对应的银联支付页面

填写银行卡密码等信息,填写完成后点击支付

支付完成后,银联会回调我们的后端回调接口或前端页面(前端页面回调地址可不传,采用手动关闭银联的页面)

接收到银行的支付回调信息后,主动调 交易查询接口 进行当前订单的支付状态

对支付回调信息与交易查询的响应信息进行处理,判断支付状态,进行后续的业务操作

**后续(退款类)**交易接口与支付流程基本一致,不过后续(退款类)交易接口是直接通过后端完成即可

三、前置工作

当接入银联无卡支付的时候,会有银联的对接人给你们申请,通过邮件发送开发所需的文件,如下:
在这里插入图片描述

开发文档ChinaPay新一代商户接入手册_20220616.pdf

公钥证书 CP.rar,需要解压此rar文件

usexxx.zip,私钥和密码压缩包,需解压后,生成私钥xxxxxxxx.sm2和密码

插件包,里面提供了Java、.net、C和PHP的插件包,java核心jar包就是chinapaysecure-sm-1.0.jar,通过此jar中的方法进行签名的生成、验签,加密解密
在这里插入图片描述

需要联系银联的对接人员开通B2B支付的测试账号

让银联方配置测试ip白名单,固定的对外IP

**注:**如果确实需要银联方提供demo参考(不建议),可让对接人员提供,demo本身基于servlet + jsp构建的eclipse项目,注释也很少,有问题建议直接跟银联方沟通,确认好自己的需求,效率会高很多。

四、正式接入

4.1 新建私钥及证书配置文件

在当前项目下,新建security.properties文件,配置好对应的公私钥及密码等信息

#报文中不参与签名的字段名称,多个字段用逗号进行分隔sign.invalid.fields=Signature,CertId#报文中签名的字段名称signature.field=Signature#是否输出调试日志,true输出,其他只输出错误日志log.info=false#私钥算法secss.privateAlg=SM2#私钥路径secss.privatePath=/xxx/xxx.sm2#私钥密码secss.privatePwd=a111111#公钥算法secss.publicAlg=SM2#公钥路径secss.publicPath=/xxx/xxx.cer#是否排除过期秘钥secss.excludeExpiredCert=true

注:公私钥路径最好采用文件的绝对路径

4.2 引入chinapaysecure-sm-1.0.jar

项目中使用maven或gradle进行依赖管理时,可先将此jar包解压至本地仓库,后通过maven的gav坐标将其引入(gradle引入依赖与此类似)

>>com.unionpay>>chinapaysecure-sm>>1.0>>

注:如果maven仓库是采用的私服仓库,上传了这个jar后,如果项目中无法获取,需要检查仓库的更新策略,具体可以联系公司内的相关人员进行处理

4.3 代码接入

3.1 核心工具类 SecssUtil 初始化

@Slf4j@Configurationpublic class SecssConfig {    @Value("${china.pay.securityUrl}")    private String securityUrl;    @Bean    public SecssUtil init(){        //执行secss初始化        SecssUtil secssUtil = new SecssUtil();        boolean bool = secssUtil.init(securityUrl);        if (bool) {            log.info("ChinaPay交易证书、验签证书初始化成功!");        } else {            log.error("ChinaPay交易证书、验签证书初始化失败:"+secssUtil.getErrCode() + "=" + secssUtil.getErrMsg());        }        return secssUtil;    }}

3.2 构建支付请求参数

@Autowiredprivate SecssUtil secssUtil;private TreeMap<String,Object> buildPayParams(SendPayReq payReq){        TreeMap<String,Object> req = new TreeMap<>();        req.put(Constants.VERSION,Constants.REQUEST_VERSION);        req.put(Constants.MER_ID,merId);        req.put(Constants.MER_ORDER_NO,payReq.getPayOrderId());        req.put(Constants.TRAN_DATE, TimeUtil.date2String(LocalDate.now()));        req.put(Constants.TRAN_TIME,TimeUtil.time2String(LocalTime.now()));        //元转分        Long orderAmt = payReq.getRemitterAmt().multiply(new BigDecimal(100)).longValue();        req.put(Constants.ORDER_AMT,orderAmt.toString());        req.put(Constants.BUSI_TYPE,Constants.BUSINESS_TYPE);        //req.put("BankInstNo", param.getAccCode()); // 支付机构号-银联在线支付(这个参数必填,如果是无卡支付前端请求,否则会失败)        //req.put("TranType",Constants.PAY_TRAN_TYPE);        //支付接口后端回调地址        req.put(Constants.MER_BG_URL,backPayUrl);        //付款支付完成后跳转至的系统前台展示地址        if (StringUtils.isNotEmpty(payReq.getMerPageUrl())){            req.put("MerPageUrl",payReq.getMerPageUrl());        }        req.put(Constants.REMOTE_ADDR,remoteAddr);        secssUtil.sign(req);        log.info("支付交易签名响应is {},信息 {}",secssUtil.getErrCode(),secssUtil.getErrMsg());        String sign = secssUtil.getSign();        req.put(Constants.SIGNATURE,sign);        return req;   }

3.3 接收支付回调

支付完成后,银联会回调我们的这个接口,具体内容及接收方法课看下第五大点中的5.3回调接口

3.4 构建交易查询接口参数

    private TreeMap<String,Object> buildQueryPayParams(AccountStorageQueryReq param){        TreeMap<String,Object> req = new TreeMap<>();        req.put(Constants.VERSION,Constants.REQUEST_VERSION);        req.put(Constants.MER_ID,merId);        req.put(Constants.MER_ORDER_NO,param.getPayOrderId());        req.put(Constants.TRAN_DATE, param.getOriTranDate());        req.put(Constants.TRAN_TYPE,Constants.QUERY_TRAN_TYPE);        req.put(Constants.BUSI_TYPE,Constants.BUSINESS_TYPE);        secssUtil.sign(req);        log.info("商务交易签名响应is {},信息 {}",secssUtil.getErrCode(),secssUtil.getErrMsg());        String sign = secssUtil.getSign();        req.put(Constants.SIGNATURE,sign);        return req;    }

五、注意点

5.1 参数类型

在向银联发起支付、交易查询、退款类等请求时,虽说文档中每个字段类型各异,但实际上所有参数都是string类型

5.2 字段解密

如果需要对银行的返回信息中某个字段需要进行解密时,需要先注意下返回的加密信息是否含有 “%”,如果含有,则需要先通过url解码对字符串解码,去除该特殊符号,然后继续采用解密方法进行解密

import java.net.URLDecoder;public String getDecData(String encData){        try {            //对加密内容进行url解码            String cardTranData = URLDecoder.decode(encData, Charsets.UTF_8);            //调用核心类方法进行解密            secssUtil.decryptData(cardTranData);            //获取解密后的内容            String cardData = secssUtil.getDecValue();            if (StringUtils.isNotEmpty(cardData)){                Map map = JSONObject.parseObject(cardData, Map.class);                return (String)map.get("CardNo");            }else {                return "";            }        }catch (Exception e){            log.warn("银行账户字段解签错误");            return "";        }  }
5.3 回调接口

我们接收银联回调的接口,地址需要能直接被银联方访问到,(公网地址,并配置银联的白名单)。采用HttpServletRequest接收参数,并用获取paramMap方法拿到所有的响应参数内容

@PostMapping("/")public ApiResult<Void> payCallback(HttpServletRequest request){    Map<String, String[]> parameterMap = request.getParameterMap();    log.info("4.1支付异步回调响应信息 is {}",parameterMap);    if (CollectionUtils.isEmpty(parameterMap)){        return ApiResult.fail("4.1支付异步回调响应信息为空");    }    return ApiResult.ok();}
5.4 请求参数签名

银联构建的所有参数都需要进行签名(签名字段本身除外),然后一起组装到请求参数中,发送至银联的对应接口

public String getSign(TreeMap<String,Object> req){        secssUtil.sign(req);        log.info("待签名信息is {},签名响应: {}",secssUtil.getErrCode(),secssUtil.getErrMsg());        String sign = secssUtil.getSign();        return sign;  }联的对应接口```javapublic String getSign(TreeMap<String,Object> req){        secssUtil.sign(req);        log.info("待签名信息is {},签名响应: {}",secssUtil.getErrCode(),secssUtil.getErrMsg());        String sign = secssUtil.getSign();        return sign;  }

来源地址:https://blog.csdn.net/weixin_42342676/article/details/131307372

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯