Android开发:app工程集成银联-退货退款功能

Posted 生活没有if-else

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android开发:app工程集成银联-退货退款功能相关的知识,希望对你有一定的参考价值。

一.前言

之前已经将银联支付功能进行了集成(服务器端戳这里客户端戳这里),暂时将退款功能搁下了,今天抽了一小段光阴把这个洞给补上了。
其实有了上一次集成支付功能的经验,对退货退款的集成就很容易实现了。本文只讲服务器端的处理,客户端根据需求写好就行。
银联官方提供了一个退货退款流程图:
这里写图片描述

所以过程主要是:服务器端组织好请求报文->银联系统进行处理->将受理结果和处理结果返回给服务器。

二.实现

我在代码中做了一些注释,所以看完代码和注释基本就没问题了。前提条件依旧是,完成好各项配置工作,可以参考服务器端的博文。
只是请注意一点:银联支付成功后会返回一个流水号,该流水号是后续操作的输入(退货、退款、查询支付状态等操作)而不是订单号(原因很简单啊,订单号是我们按一定规则生成的,银联系统肯定不认),所以必须将该流水号和我们需要操作的订单进行绑定,当然最好的方式就是在订单表里增加一个流水号字段。

1.第一步

组织请求报文,向银联后台发起退货退款请求。

/**
     * 退款流程
     * @param orderId  //需要退货退款的订单ID
     * @param request
     * @param response
     * @throws UnsupportedEncodingException 
     */
    @RequestMapping(value = "/pay/refund/{orderId}")
    @ResponseBody
    public JSONObject refund(@PathVariable("orderId") String orderId,HttpServletRequest request, HttpServletResponse response) throws UnsupportedEncodingException 
    {
        //防止乱码,根据业务需求,这两句可有可无
        request.setCharacterEncoding(DemoBase.encoding_UTF8);
        response.setContentType("text/html; charset="+ DemoBase.encoding_UTF8);
        //json用于将数据返回给客户端
        JSONObject json = new JSONObject();
        System.out.println("退款开始");
        //获得该订单的信息
        Order order  = orderDAO.getOrder(orderId);
        if(order==null)
        {
            json.put("result", "0");
            return json;
        }   
        //获得该订单的流水号
        String orderQueryId = order.getOrderQueryId();
        //获得该订单的总价
        String orderOilTotalPrice = order.getOrderOilTotalPrice();

        Map<String, String> data = new HashMap<String, String>();

        /***银联全渠道系统,产品参数,除了encoding自行选择外其他不需修改***/
        data.put("version", DemoBase.version);               //版本号
        data.put("encoding", DemoBase.encoding_UTF8);             //字符集编码 可以使用UTF-8,GBK两种方式
        data.put("signMethod", "01");                        //签名方法 目前只支持01-RSA方式证书加密
        data.put("txnType", "04");                           //交易类型 04-退货       
        data.put("txnSubType", "00");                        //交易子类型  默认00      
        data.put("bizType", "000201");                       //业务类型
        data.put("channelType", "08");                       //渠道类型,07-PC,08-手机     

        /***商户接入参数***/
        data.put("merId", DemoBase.merId);                //商户号码,请改成自己申请的商户号或者open上注册得来的777商户号测试
        data.put("accessType", "0");                         //接入类型,商户接入固定填0,不需修改       
        //一定要注意,该orderId并不是我们自己的订单id,而是退款申请这条业务的id,银联提供的DemoBase.getOrderId()是根据系统时间生成的。
        data.put("orderId", DemoBase.getOrderId());          //商户订单号,8-40位数字字母,不能含“-”或“_”,可以自行定制规则,重新产生,不同于原消费      
        data.put("txnTime", DemoBase.getCurrentTime());      //订单发送时间,格式为YYYYMMDDhhmmss,必须取当前时间,否则会报txnTime无效       
        data.put("currencyCode", "156");                     //交易币种(境内商户一般是156 人民币)     
        //一定注意,退款金额必须是整数,而且单位是分。
        data.put("txnAmt", Double.valueOf(orderOilTotalPrice).intValue()*100+"");                          //****退货金额,单位分,不要带小数点。退货金额小于等于原消费金额,当小于的时候可以多次退货至退货累计金额等于原消费金额       
        //data.put("txnAmt",orderOilTotalPrice);
        //这个透传字段很有用,因为前面的orderId已经不是我们自己的订单id了,而我们在后台通知里肯定还需要这个订单id,因为我们需要修改该订单的状态,可是data中根本不能put我们自己的订单id,那么这个字段就是用来存放一些我们想传递给后台通知的数据,因为data中的数据都会完整的全部返回给后台通知,我这里只是在透传字段里放了一个orderId,如果有更多的数据需要传递,只需要用map或者json存储数据,然后转成String就可以了
        data.put("reqReserved", orderId);                    //请求方保留域,透传字段(可以实现商户自定义参数的追踪)本交易的后台通知,对本交易的交易状态查询交易、对账文件中均会原样返回,商户可以按需上传,长度为1-1024个字节  
        //后台通知地址必须是真实ip,因为银联后台要将通知post到这个后台地址。  
        data.put("backUrl", DemoBase.backUrl);               //后台通知地址,后台通知参数详见open.unionpay.com帮助中心 下载  产品接口规范  网关支付产品接口规范 退货交易 商户通知,其他说明同消费交易的后台通知

        /***要调通交易以下字段必须修改***/
        //流水号,这才是银联后台认识的标识符,该流水号是在支付成功后获得的。
        data.put("origQryId", orderQueryId);      //****原消费交易返回的的queryId,可以从消费交易后台通知接口中或者交易状态查询接口中获取

        /**请求参数设置完毕,以下对请求参数进行签名并发送http post请求,接收同步应答报文------------->**/
        Map<String, String> reqData  = AcpService.sign(data,DemoBase.encoding_UTF8);        //报文中certId,signature的值是在signData方法中获取并自动赋值的,只要证书配置正确即可。
        String url = SDKConfig.getConfig().getBackRequestUrl();                                 //交易请求url从配置文件读取对应属性文件acp_sdk.properties中的 acpsdk.backTransUrl
        Map<String, String> rspData = AcpService.post(reqData, url,DemoBase.encoding_UTF8);//这里调用signData之后,调用submitUrl之前不能对submitFromData中的键值对做任何修改,如果修改会导致验签不通过

        /**对应答码的处理,请根据您的业务逻辑来编写程序,以下应答码处理逻辑仅供参考------------->**/
        //应答码规范参考open.unionpay.com帮助中心 下载  产品接口规范  《平台接入接口规范-第5部分-附录》
        if(!rspData.isEmpty()){
            if(AcpService.validate(rspData, DemoBase.encoding_UTF8)){
                LogUtil.writeLog("验证签名成功");
                String respCode = rspData.get("respCode") ;
                if(("00").equals(respCode)){
                    //交易已受理(不代表交易已成功),等待接收后台通知更新订单状态,也可以主动发起 查询交易确定交易状态。
                    //TODO
                    json.put("result", "1");
                    return json;
                }else if(("03").equals(respCode)||
                         ("04").equals(respCode)||
                         ("05").equals(respCode)){
                    //后续需发起交易状态查询交易确定交易状态
                    //TODO
                }else{
                    //其他应答码为失败请排查原因
                    //TODO
                }
            }else{
                LogUtil.writeErrorLog("验证签名失败");
                //TODO 检查验证签名失败的原因
            }
        }else{
            //未返回正确的http状态
            LogUtil.writeErrorLog("未获取到返回报文或返回http状态码非200");
        }
        json.put("result", "0");
        return json;
    }

2.第二步

后台通知地址中接收银联后台的处理结果通知,在上一篇中已经写了后台通知处理,这次加上退款功能后就稍微修改了一下。

/**
     * 后台通知处理
     * @param request
     * @param response
     */
    @RequestMapping(value = "/pay/backRcvResponse")
    @ResponseBody
    public void backRcvResponse(HttpServletRequest request, HttpServletResponse response) 
    {
        System.out.println("后台通知验签开始");
        //return AcpService.validateAppResponse(sign, DemoBase.encoding_UTF8);
        //System.out.println("验签开始");
        String encoding = request.getParameter(SDKConstants.param_encoding);
        // 获取银联通知服务器发送的后台通知参数
        Map<String, String> reqParam = Tool.getAllRequestParam(request);

        LogUtil.printRequestLog(reqParam);

        Map<String, String> valideData = null;
        try
        {
            if (null != reqParam && !reqParam.isEmpty()) {
                Iterator<Entry<String, String>> it = reqParam.entrySet().iterator();
                valideData = new HashMap<String, String>(reqParam.size());
                while (it.hasNext()) {
                    Entry<String, String> e = it.next();
                    String key = (String) e.getKey();
                    String value = (String) e.getValue();
                    value = new String(value.getBytes(encoding), encoding);
                    valideData.put(key, value);
                }
            }

            //重要!验证签名前不要修改reqParam中的键值对的内容,否则会验签不过
            if (!AcpService.validate(valideData, encoding)) {
                LogUtil.writeLog("验证签名结果[失败].");
                //验签失败,需解决验签问题

            } else {
                LogUtil.writeLog("验证签名结果[成功].");
                //【注:为了安全验签成功才应该写商户的成功处理逻辑】交易成功,更新商户订单状态
                //String orderId =valideData.get("orderId"); //获取后台通知的数据
                //Order order = orderDAO.getOrder(orderId);
                //获取交易类型,可参考官方文档
                String txnType = valideData.get("txnType");
                System.out.println(Integer.valueOf(txnType));
                Order order;
                switch(Integer.valueOf(txnType))//交易类型
                {
                    case 01://消费
                        String orderId =valideData.get("orderId"); //获取后台通知的数据,其他字段也可用类似方式获取
                        String payTime = valideData.get("txnTime");
                        String orderQueryId = valideData.get("queryId");
                        //String respCode =valideData.get("respCode"); //获取应答码,收到后台通知了respCode的值一般是00,可以不需要根据这个应答码判断。
                        //if(orderId!=null&&!"".equals(orderId))
                        //{
                            order = orderDAO.getOrder(orderId);
                            if(order!=null)
                            {
                                System.out.println("更新支付状态:"+orderId);
                                System.out.println("payTime"+payTime);
                                order.setOrderPayStatus(1);
                                order.setOrderPayTime(payTime+".0");
                                order.setOrderQueryId(orderQueryId);
                                //order.setOrderPayTime(new SimpleDateFormat(" yyyy-MM-dd HH:mm:ss ").parse(payTime));
                                boolean sucess = orderDAO.update(order);    
                                System.out.println("sucess"+sucess);
                            }
                        //}
                        break;
                    case 04://退货&退款
                    //获得透传字段的数据,我这里就是订单id
                        order = orderDAO.getOrder(valideData.get("reqReserved"));
                        if(order!=null)
                        {
                        //更新订单状态
                            order.setOrderPayStatus(2);
                            order.setOrderStatus(2);
                            orderDAO.update(order);
                        }
                        break;
                    default:
                        break;
                }
            }
            LogUtil.writeLog("BackRcvResponse接收后台通知结束");

            //返回给银联服务器http 200  状态码
            response.getWriter().print("ok");
        }
        catch(Exception e){}
    }

ok,还是看一下效果吧。respCode = 00表示成功。
这里写图片描述

三.问题

不会遇到问题是不可能的,当然按照我上面代码的实现的也只是能把基本流程跑通,从安全性上来讲,还差太多…
1.退款金额必须是整数
比如下面图中的情况:
这里写图片描述
2.data中的orderId不是自己的orderId,而是本次退款交易的id。
3.后台通知地址一定是真是ip,localhost不行,回路地址也不行。

以上是关于Android开发:app工程集成银联-退货退款功能的主要内容,如果未能解决你的问题,请参考以下文章

集成银联云闪付SDK实践-2020-07-27

微信小程序里退货退款填了只退款

用商品优惠券购买的商品交易成功后退货退款,退优惠券吗

Android开发:app工程集成mob短信验证码功能

淘宝使用了优惠券后怎么申请退款

退还跟退回有啥区别?退回和退还有啥不同