淘东电商项目(59) -聚合支付(集成银联支付)

简介: 淘东电商项目(59) -聚合支付(集成银联支付)

引言

本文代码已提交至Github(版本号:31cef39a6e92b1d5857ed7432693df23bb681fe5),有兴趣的同学可以下载来看看:https://github.com/ylw-github/taodong-shop

在上一篇博客《淘东电商项目(58) -聚合支付(基于设计模式自动跳转支付接口)》,已经讲解完了如下图的第1 - 5个步骤,接下来本文要讲解集成银联支付,也就是下面的第6-8个步骤。

本文目录结构:

l____引言

l____ 1. 集成银联支付

l________ 1.1 银联支付插件

l________ 1.2 银联支付插件的使用

l____ 2. 测试

l____ 3. 其它说明

1. 集成银联支付

1.1 银联支付插件

①首先新建支付插件模块,支付插件模块下有银联支付插件:

②把银联支付demo里面的代码拷贝过来,如下:

③新建InitUnionPayProject启动类,此类在程序运行时会自动加载,主要加载acp_sdk.properties文件里面的配置信息,代码如下:

/**
 * description: 银联支付项目初始化
 * create by: YangLinWei
 * create time: 2020/5/15 9:45 上午
 */
@Component
public class InitUnionPayProject implements ApplicationRunner {
  // springboot 项目启动的时候 执行该方法
  @Override
  public void run(ApplicationArguments args) throws Exception {
    SDKConfig.getConfig().loadPropertiesFromSrc();
  }
}

acp_sdk.properties配置文件,修改证书路径,注意这里的证书路径必须是绝对路径,不能写相对路径(证书的申请,查看我前面写的文章https://yanglinwei.blog.csdn.net/article/details/106013626):

1.2 银联支付插件的使用

①支付服务(taodong-shop-service-pay)引入银联支付插件:

<dependency>
    <groupId>com.ylw</groupId>
    <artifactId>taodong-shop-union-plugin</artifactId>
    <version>1.0-RELEASE</version>
</dependency>

②编写银联支付策略代码,完整代码如下:

/**
 * description: 银联支付渠道实现
 * create by: YangLinWei
 * create time: 2020/5/13 4:41 下午
 */
@Slf4j
public class UnionPayStrategy implements PayStrategy {
    @Override
    public String toPayHtml(PaymentChannelEntity paymentChannel, PayMentTransacDTO payMentTransacDTO) {
        log.info(">>>>>>>>银联支付组装参数开始<<<<<<<<<<<<");
        Map<String, String> requestData = new HashMap<String, String>();
        /*** 银联全渠道系统,产品参数,除了encoding自行选择外其他不需修改 ***/
        requestData.put("version", UnionPayBase.version); // 版本号,全渠道默认值
        requestData.put("encoding", UnionPayBase.encoding); // 字符集编码,可以使用UTF-8,GBK两种方式
        requestData.put("signMethod", SDKConfig.getConfig().getSignMethod()); // 签名方法
        requestData.put("txnType", "01"); // 交易类型 ,01:消费
        requestData.put("txnSubType", "01"); // 交易子类型, 01:自助消费
        requestData.put("bizType", "000201"); // 业务类型,B2C网关支付,手机wap支付
        requestData.put("channelType", "07"); // 渠道类型,这个字段区分B2C网关支付和手机wap支付;07:PC,平板
        // 08:手机
        /*** 商户接入参数 ***/
        String merchantId = paymentChannel.getMerchantId();
        requestData.put("merId", merchantId); // 商户号码,请改成自己申请的正式商户号或者open上注册得来的777测试商户号
        requestData.put("accessType", "0"); // 接入类型,0:直连商户
        String paymentId = payMentTransacDTO.getPaymentId();
        // 在微服务电商项目中 订单系统(orderId)   支付系统 支付id
        requestData.put("orderId", paymentId); // 商户订单号,8-40位数字字母,不能含“-”或“_”,可以自行定制规则
        requestData.put("txnTime", format(payMentTransacDTO.getCreatedTime())); // 订单发送时间,取系统时间,格式为YYYYMMDDhhmmss,必须取当前时间,否则会报txnTime无效
        requestData.put("currencyCode", "156"); // 交易币种(境内商户一般是156 人民币)
        Long payAmount = payMentTransacDTO.getPayAmount();
        requestData.put("txnAmt", payAmount + ""); // 交易金额,单位分,不要带小数点
        // requestData.put("reqReserved", "透传字段");
        // //请求方保留域,如需使用请启用即可;透传字段(可以实现商户自定义参数的追踪)本交易的后台通知,对本交易的交易状态查询交易、对账文件中均会原样返回,商户可以按需上传,长度为1-1024个字节。出现&={}[]符号时可能导致查询接口应答报文解析失败,建议尽量只传字母数字并使用|分割,或者可以最外层做一次base64编码(base64编码之后出现的等号不会导致解析失败可以不用管)。
        //商品名称 或者商品描述
        requestData.put("riskRateInfo", payMentTransacDTO.getProductName());
        //
        // 前台通知地址 (需设置为外网能访问 http https均可),支付成功后的页面 点击“返回商户”按钮的时候将异步通知报文post到该地址
        // 如果想要实现过几秒中自动跳转回商户页面权限,需联系银联业务申请开通自动返回商户权限
        // 异步通知参数详见open.unionpay.com帮助中心 下载 产品接口规范 网关支付产品接口规范 消费交易 商户通知\
        String syncUrl = paymentChannel.getSyncUrl();
        requestData.put("frontUrl", syncUrl);
        // 后台通知地址(需设置为【外网】能访问 http
        // https均可),支付成功后银联会自动将异步通知报文post到商户上送的该地址,失败的交易银联不会发送后台通知
        // 后台通知参数详见open.unionpay.com帮助中心 下载 产品接口规范 网关支付产品接口规范 消费交易 商户通知
        // 注意:1.需设置为外网能访问,否则收不到通知 2.http https均可 3.收单后台通知后需要10秒内返回http200或302状态码
        // 4.如果银联通知服务器发送通知后10秒内未收到返回状态码或者应答码非http200,那么银联会间隔一段时间再次发送。总共发送5次,每次的间隔时间为0,1,2,4分钟。
        // 5.后台通知地址如果上送了带有?的参数,例如:http://abc/web?a=b&c=d
        // 在后台通知处理程序验证签名之前需要编写逻辑将这些字段去掉再验签,否则将会验签失败
        String asynUrl = paymentChannel.getAsynUrl();
        requestData.put("backUrl", asynUrl);
        // 订单超时时间。
        // 超过此时间后,除网银交易外,其他交易银联系统会拒绝受理,提示超时。
        // 跳转银行网银交易如果超时后交易成功,会自动退款,大约5个工作日金额返还到持卡人账户。
        // 此时间建议取支付时的北京时间加15分钟。
        // 超过超时时间调查询接口应答origRespCode不是A6或者00的就可以判断为失败。
        requestData.put("payTimeout",
                new SimpleDateFormat("yyyyMMddHHmmss").format(new Date().getTime() + 15 * 60 * 1000));
        //
        //
        // 报文中特殊用法请查看 PCwap网关跳转支付特殊用法.txt
        //
        //
        /** 请求参数设置完毕,以下对请求参数进行签名并生成html表单,将表单写入浏览器跳转打开银联页面 **/
        Map<String, String> submitFromData = AcpService.sign(requestData, UnionPayBase.encoding); // 报文中certId,signature的值是在signData方法中获取并自动赋值的,只要证书配置正确即可。
        String requestFrontUrl = SDKConfig.getConfig().getFrontRequestUrl(); // 获取请求银联的前台地址:对应属性文件acp_sdk.properties文件中的acpsdk.frontTransUrl
        String html = AcpService.createAutoFormHtml(requestFrontUrl, submitFromData, UnionPayBase.encoding); // 生成自动跳转的Html表单
        LogUtil.writeLog("打印请求HTML,此为请求报文,为联调排查问题的依据:" + html);
        // 将生成的html写到浏览器中完成自动跳转打开银联支付页面;这里调用signData之后,将html写到浏览器跳转到银联页面之前均不能对html中的表单项的名称和值进行修改,如果修改会导致验签不通过
        return html;
    }
    private String format(Date timeDate) {
        String date = new java.text.SimpleDateFormat("yyyyMMddHHmmss").format(timeDate);
        return date;
    }
}

③编写前台通知和后台通知的代码,如下:

/**
 * description: 银联前台通知和后台通知地址
 * create by: YangLinWei
 * create time: 2020/5/15 11:04 上午
 */
@Controller
public class UnionResponseController {
    @RequestMapping("/frontRcvResponse")
    @ResponseBody
    public String frontRcvResponse(HttpServletRequest req, HttpServletResponse resp) throws UnsupportedEncodingException {
        LogUtil.writeLog("FrontRcvResponse前台接收报文返回开始");
        String encoding = req.getParameter(SDKConstants.param_encoding);
        LogUtil.writeLog("返回报文中encoding=[" + encoding + "]");
        String pageResult = "";
        Map<String, String> respParam = getAllRequestParam(req);
        // 打印请求报文
        LogUtil.printRequestLog(respParam);
        Map<String, String> valideData = null;
        StringBuffer page = new StringBuffer();
        if (null != respParam && !respParam.isEmpty()) {
            Iterator<Map.Entry<String, String>> it = respParam.entrySet()
                    .iterator();
            valideData = new HashMap<String, String>(respParam.size());
            while (it.hasNext()) {
                Map.Entry<String, String> e = it.next();
                String key = (String) e.getKey();
                String value = (String) e.getValue();
                value = new String(value.getBytes(encoding), encoding);
                page.append("<tr><td width=\"30%\" align=\"right\">" + key
                        + "(" + key + ")</td><td>" + value + "</td></tr>");
                valideData.put(key, value);
            }
        }
        if (!AcpService.validate(valideData, encoding)) {
            page.append("<tr><td width=\"30%\" align=\"right\">验证签名结果</td><td>失败</td></tr>");
            LogUtil.writeLog("验证签名结果[失败].");
        } else {
            page.append("<tr><td width=\"30%\" align=\"right\">验证签名结果</td><td>成功</td></tr>");
            LogUtil.writeLog("验证签名结果[成功].");
            System.out.println(valideData.get("orderId")); //其他字段也可用类似方式获取
            String respCode = valideData.get("respCode");
            //判断respCode=00、A6后,对涉及资金类的交易,请再发起查询接口查询,确定交易成功后更新数据库。
        }
        return page.toString();
    }
    @RequestMapping("/BackRcvResponse")
    @ResponseBody
    public String BackRcvResponse(HttpServletRequest req, HttpServletResponse resp) {
        LogUtil.writeLog("BackRcvResponse接收后台通知开始");
        String encoding = req.getParameter(SDKConstants.param_encoding);
        // 获取银联通知服务器发送的后台通知参数
        Map<String, String> reqParam = getAllRequestParam(req);
        LogUtil.printRequestLog(reqParam);
        //重要!验证签名前不要修改reqParam中的键值对的内容,否则会验签不过
        if (!AcpService.validate(reqParam, encoding)) {
            LogUtil.writeLog("验证签名结果[失败].");
            //验签失败,需解决验签问题
        } else {
            LogUtil.writeLog("验证签名结果[成功].");
            //【注:为了安全验签成功才应该写商户的成功处理逻辑】交易成功,更新商户订单状态
            String orderId =reqParam.get("orderId"); //获取后台通知的数据,其他字段也可用类似方式获取
            String respCode = reqParam.get("respCode");
            //判断respCode=00、A6后,对涉及资金类的交易,请再发起查询接口查询,确定交易成功后更新数据库。
        }
        LogUtil.writeLog("BackRcvResponse接收后台通知结束");
        //返回给银联服务器http 200  状态码
        return "ok";
    }
    /**
     * 获取请求参数中所有的信息
     * 当商户上送frontUrl或backUrl地址中带有参数信息的时候,
     * 这种方式会将url地址中的参数读到map中,会导多出来这些信息从而致验签失败,这个时候可以自行修改过滤掉url中的参数或者使用getAllRequestParamStream方法。
     * @param request
     * @return
     */
    public static Map<String, String> getAllRequestParam(
            final HttpServletRequest request) {
        Map<String, String> res = new HashMap<String, String>();
        Enumeration<?> temp = request.getParameterNames();
        if (null != temp) {
            while (temp.hasMoreElements()) {
                String en = (String) temp.nextElement();
                String value = request.getParameter(en);
                res.put(en, value);
                // 在报文上送时,如果字段的值为空,则不上送<下面的处理为在获取所有参数数据时,判断若值为空,则删除这个字段>
                if (res.get(en) == null || "".equals(res.get(en))) {
                    // System.out.println("======为空的字段名===="+en);
                    res.remove(en);
                }
            }
        }
        return res;
    }
}

好了代码到此基本写完,下面开始测试。

2. 测试

首先启动项目(Eureka注册中心、单点服务中心、会员服务、支付服务、支付门户):

①首先模拟提交订单,获取token,浏览器访问:http://localhost:8600/cratePayToken?payAmount=999&orderId=20200513141452&userId=27&productName=辣条,获取tokenpay_d520140b06e249e0bf67692c3802fde1:

②模拟跳转到支付详情页,浏览器访问:http://localhost:8079/pay?payToken=pay_d520140b06e249e0bf67692c3802fde1

③点击银联支付,可以看到自动跳转到银联支付页面:


④填写信息支付,可以看到支付成功:

⑤点击返回商户,可以看到前台通知内容如下:

3. 其它说明

INSERT INTO `taodong-pay`.`payment_channel`(`ID`, `CHANNEL_NAME`, `CHANNEL_ID`, `MERCHANT_ID`, `SYNC_URL`, `ASYN_URL`, `PUBLIC_KEY`, `PRIVATE_KEY`, `CHANNEL_STATE`, `REVISION`, `CREATED_BY`, `CREATED_TIME`, `UPDATED_BY`, `UPDATED_TIME`, `CLASS_ADDRES`) VALUES (1, '银联支付', 'yinlian_pay', '777290058110048', 'http://localhost:8600/frontRcvResponse', 'http://swx5c3.natappfree.cc/BackRcvResponse', NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, 'com.ylw.service.pay.strategy.impl.UnionPayStrategy');


目录
相关文章
|
3月前
|
监控 前端开发 安全
如何集成第三方支付API到电商网站
在电商网站中,集成第三方支付API是确保交易安全、提升用户体验的关键步骤。本文详细介绍了从选择支付提供商到上线监控的全流程,涵盖代码示例与实用建议,助您高效实现支付功能。
140 0
|
3月前
|
监控 测试技术 API
电商API常见错误排查指南:避免集成陷阱
API集成是电商开发的核心,但常因认证、数据、限流等问题引发错误,影响项目进度与用户体验。本文详解常见错误类型、排查步骤与预防策略,结合Python示例指导开发者高效应对。通过日志分析、数据校验、速率监控等手段,帮助您系统化规避集成陷阱,提升开发效率与系统稳定性。
159 0
|
3月前
|
缓存 监控 安全
电商API集成入门:从零开始搭建高效接口
在数字化电商时代,API集成成为企业提升效率、实现系统互联的关键。本文从零开始,逐步讲解如何搭建高效、可靠的电商API接口,适合初学者学习。内容涵盖API基础、认证安全、请求处理、性能优化等核心步骤,并提供Python代码示例与数学公式辅助理解。通过实践,读者可掌握构建优质电商API的技巧,提升用户体验与系统性能。
150 0
|
17天前
|
安全 Java 数据库
SpringSecurity认证授权及项目集成
本文介绍了基于Spring Security的权限管理框架,涵盖认证、授权与鉴权核心概念,通过快速入门示例演示集成流程,并结合数据库实现用户认证。进一步扩展实现正常登录,JWT登录及鉴权管理器,实现灵活的安全控制,适用于前后端分离项目中的权限设计与实践。
145 4
|
1月前
|
资源调度 JavaScript 前端开发
在Vue 3项目中集成Element Plus组件库的步骤
总结起来,在集成过程当中我们关注于库本身提供功能与特性、环境搭建与依赖管理、模块化编程思想以及前端工程化等方面知识点;同时也涵盖前端性能优化(比如上文提及“按需加载”)与定制化开发(例如“自定义主题”)等高级话题.
126 16
|
3月前
|
JSON 分布式计算 大数据
springboot项目集成大数据第三方dolphinscheduler调度器
springboot项目集成大数据第三方dolphinscheduler调度器
165 3
|
3月前
|
Java 关系型数据库 数据库连接
Spring Boot项目集成MyBatis Plus操作PostgreSQL全解析
集成 Spring Boot、PostgreSQL 和 MyBatis Plus 的步骤与 MyBatis 类似,只不过在 MyBatis Plus 中提供了更多的便利功能,如自动生成 SQL、分页查询、Wrapper 查询等。
288 3
|
3月前
|
Java 关系型数据库 MySQL
springboot项目集成dolphinscheduler调度器 实现datax数据同步任务
springboot项目集成dolphinscheduler调度器 实现datax数据同步任务
378 2
|
3月前
|
分布式计算 Java 大数据
springboot项目集成dolphinscheduler调度器 可拖拽spark任务管理
springboot项目集成dolphinscheduler调度器 可拖拽spark任务管理
191 2
|
3月前
|
物联网 Linux 开发者
快速部署自己私有MQTT-Broker-下载安装到运行不到一分钟,快速简单且易于集成到自己项目中
本文给物联网开发的朋友推荐的是GMQT,让物联网开发者快速拥有合适自己的MQTT-Broker,本文从下载程序到安装部署手把手教大家安装用上私有化MQTT服务器。
929 5
下一篇
日志分析软件