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不行,回路地址也不行。

时间: 2024-11-02 23:50:40

Android开发:app工程集成银联-退货退款功能的相关文章

Android开发:app工程集成银联支付功能(客户端)

Android开发:app工程集成银联支付功能(客户端) email:[email protected] 上一篇博文完成了服务器端的集成,可参考: Android开发:app工程集成银联支付功能(服务器端).这一篇博文完成客户端的集成. 一.功能描述 银联支付流程如下所示: 上一篇尝试了tn的获取,本篇将跑通整个流程. 二.实现部分 先说一下我的IDE是as(Android Studio)+win7 64位 2.1配置,依旧是配置 按照官方说明文档,就可以完成,我在这里贴上结构图,因为as的特殊

Android开发:app工程集成银联支付功能(服务器端)

一功能描述 二实现过程 1下载银联支付SDK和Demo 1银联商家服务地址httpsopenunionpaycomajwebindex 2下载的文件如下 2集成过程 1先试官方Demo 2集成到自己的工程里 一.功能描述 因为是自己开发了一个app应用,没资格去申请微信支付和支付宝支付,于是就采用了银联支付功能,银联支付分为了两种环境:测试环境和生产环境,一般前期开发的时候都是使用测试环境,数据都是测试数据,不会发生真实交易.第一次做Android项目+IDE为Android Studio+第一

Android开发App工程结构搭建

本文算是一篇漫谈,谈一谈关于android开发中工程初始化的时候如何在初期我们就能搭建一个好的架构.      关于android架构,因为手机的限制,目前我觉得也确实没什么大谈特谈的,但是从开发的角度,看到整齐的代码,优美的分层总是一种舒服的享受的.      从艺术的角度看,其实我们是在追求一种美. 本文先分析几个当今比较流行的android软件包,最后我们汲取其中觉得优秀的部分,搭建我们自己的通用android工程模板.      1. 微盘      2. 久忆日记      3.网易新

0-2岁的app开发人员必读,Android开发APP前的准备事项

随着移动互联网的兴起,各行各业对移动应用的需求越来越大,从事APP开发的人也越来越多,APP开发行业可以说是方兴未艾.APP开发是比较复杂的事情,涉及产品.美工设计.服务器端开发.Android/iOS开发.软件测试和项目管理等各方面.这些方面都是相互关联的,如果要做好一个APP,需要对上述各方面都有所了解. 在实际工作中,许多从别的行业转行从事APP开发或从事APP开发一两年的人员,对APP开发的基础知识不甚了解,需要关注的地方都没有考虑到,导致在开发APP的过程中犯了许多低级错误,而这些本来

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

一.前言 现在的app基本上都需要用到短信功能,注册时或者有消息通知时需要给用户发送一条短信,但是对于个人开发者来说,去买第三方的短信服务实在是有点奢侈,很好的是mob为我们提供了免费的短信验证码服务功能,我不是打广告,的确觉得这项服务很不错.那么下面就简单讲一下如何在自己的工程里集成mob的短信功能,其实整个流程并不复杂,只是个人觉得mob的官方文档有点小乱,官方Demo也有点小复杂,同时有一些细节地方容易被忽视,也会导致一些问题. PS:太喜欢mob的logo了. 二.实现过程 本篇只涉及A

Xamarin.Android开发-APP欢迎页面

1.添加Xamarin.Android.Support.v4.dll 2.使用ViewPager控件 3.为ViewPager设置适配器,声明适配器继承PagerAdapter,重写3个方法,1个属性 ViewPager mVP = FindViewById<ViewPager>(Resource.Id.mYvp);mVP.Adapter = new MyPageAdapter(this); public override int Count{} public override Java.La

Android开发app如何设定应用图标下的应用名称为汉字以及自定义图标

一.应用名称为汉字 二.自定义图标

Android开发教程--自定义接听/挂断电话功能

1.首先在manifest中加入如下的权限 <uses-permission android:name="android.permission.READ_PHONE_STATE" /> <uses-permission android:name="android.permission.MODIFY_PHONE_STATE" > </uses-permission> <uses-permission android:name=

Android开发——实现TabHost 随手滑动切换选项卡功能(绝对实用)

以前用TabHost只是点击导航栏选项卡才进行切换,今天试了下手势滑动进行切换,搜了好多资料感觉特别乱,花了好长时间整理了一下终于有效果了,自己写了一个Demo. 程序清单1:布局文件 说明:和我们写Tabhost布局文件一样 activity_main.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout  xmlns:android="http://schemas.andro