基于支付宝微信通知的一种个人收款回调方案

以下内容仅供学习交流,请勿直接用于商业活动

============细节问题===============

经过长时间实践,发现微信和支付宝通知并不是很稳定,有时候会有漏消息的可能,这样对业务会造成很大影响,有些银行的二维码收款可以订阅短信通知,监听短信通知十分稳定,

经过长达数月的测试,没有一起遗漏,所以值得试试,唯一的缺点是短信接收有点延迟,有时长达1分钟左右

==========================================

最近闲来无事,看到网上有一些免签支付回调的服务商,当时感觉很新奇,于是自己动手看看怎么玩的,先看成果

App上监听通知并向服务器POST支付信息

服务端的支付订单表

下面说原理及流程

1.App上使用NotificationListenerService监听通知栏通知,一旦微信支付或者支付宝收款收到消息,读取消息的内容,然后使用正则匹配金额

2.App读取到金额后,构造支付订单,支付订单包含:订单号(App自己生成,不是真实的支付方订单号),金额,App端标识,支付方式,签名(保证数据不被篡改)

3.App将订单POST到填写的URL中

4.服务端收到订单信息,先校验签名是否相符,再查看订单是否存在(防止重放攻击),验证通过后存入数据库,并向指定的回调地址发起请求

5.服务端如果向指定的回调地址发起请求失败,使用定时任务重复发起回调,直到回调成功或达到指定次数

以上就是全部过程,服务端使用springboot,可以很快速搭建

当然为了保证可靠性需要给App加固,防止退出,还有这种只能读取到金额,其他信息一无所知,有些局限性

上代码

App部分

继承NotificationListenerService重写onNotificationPosted方法

//来通知时的调用
    @Override
    public void onNotificationPosted(StatusBarNotification sbn) {
        Notification notification = sbn.getNotification();
        if (notification == null) {
            return;
        }
        Bundle extras = notification.extras;
        if (extras != null) {
            //包名
            String pkg = sbn.getPackageName();
            // 获取通知标题
            String title = extras.getString(Notification.EXTRA_TITLE, "");
            // 获取通知内容
            String content = extras.getString(Notification.EXTRA_TEXT, "");
            Log.i(TAG, String.format("收到通知,包名:%s,标题:%s,内容:%s", pkg, title, content));
            //处理
            processOnReceive(pkg, title, content);
        }
    }

/**
     * 消息来时处理
     *
     * @param pkg
     * @param title
     * @param content
     */
    private void processOnReceive(String pkg, String title, String content) {
        if (!AppConstants.LISTEN_RUNNING) {
            return;
        }
        if ("com.eg.android.AlipayGphone".equals(pkg)) {
            //支付宝
            if (checkMsgValid(title,content,"alipay") && StringUtils.isNotBlank(parseMoney(content))) {
                TreeMap<String, String> paramMap = new TreeMap<>();
                paramMap.put("title", title);
                paramMap.put("content", content);
                paramMap.put("identifier", AppConstants.CLIENT_IDENTIFIER);
                paramMap.put("orderid", CommonUtils.randomCharSeq());
                paramMap.put("gateway", "alipay");
                String sign = CommonUtils.calcSign(paramMap, AppConstants.SIGN_KEY);
                if (StringUtils.isBlank(sign)) {
                    Log.e(TAG, "签名错误");
                    return;
                }
                HttpTask task = new HttpTask();
                task.setOnAsyncResponse(this);
                String json = new Gson().toJson(paramMap);
                task.execute(AppConstants.POST_URL, "sign=" + sign, json);
            }
        } else if ("com.tencent.mm".equals(pkg)) {
            //微信
            if (checkMsgValid(title, content, "wxpay") && StringUtils.isNotBlank(parseMoney(content))) {
                TreeMap<String, String> paramMap = new TreeMap<>();
                paramMap.put("title", title);
                paramMap.put("content", content);
                paramMap.put("identifier", AppConstants.CLIENT_IDENTIFIER);
                paramMap.put("orderid", CommonUtils.randomCharSeq());
                paramMap.put("gateway", "wxpay");
                String sign = CommonUtils.calcSign(paramMap, AppConstants.SIGN_KEY);
                if (StringUtils.isBlank(sign)) {
                    Log.e(TAG, "签名错误");
                    return;
                }
                HttpTask task = new HttpTask();
                task.setOnAsyncResponse(this);
                String json = new Gson().toJson(paramMap);
                task.execute(AppConstants.POST_URL, "sign=" + sign, json);
            }
        }
    }

/**
     * 解析内容字符串,提取金额
     *
     * @param content
     * @return
     */
    private static String parseMoney(String content) {
        Pattern pattern = Pattern.compile("收款(([1-9]\\d*)|0)(\\.(\\d){0,2})?元");
        Matcher matcher = pattern.matcher(content);
        if (matcher.find()) {
            String tmp = matcher.group();
            Pattern patternnum = Pattern.compile("(([1-9]\\d*)|0)(\\.(\\d){0,2})?");
            Matcher matchernum = patternnum.matcher(tmp);
            if (matchernum.find())
                return matchernum.group();
        }
        return null;
    }

    /**
     * 验证消息的合法性,防止非官方消息被处理
     *
     * @param title
     * @param content
     * @param gateway
     * @return
     */
    private static boolean checkMsgValid(String title, String content, String gateway) {
        if ("wxpay".equals(gateway)) {
            //微信支付的消息格式
            //1条:标题:微信支付,内容:微信支付收款0.01元(朋友到店)
            //多条:标题:微信支付,内容:[4条]微信支付: 微信支付收款1.01元(朋友到店)
            Pattern pattern = Pattern.compile("^((\\[\\+?\\d+条])?微信支付:|微信支付收款)");
            Matcher matcher = pattern.matcher(content);
            return "微信支付".equals(title) && matcher.find();
        } else if ("alipay".equals(gateway)) {
            //支付宝的消息格式,标题:支付宝通知,内容:支付宝成功收款1.00元。
            return "支付宝通知".equals(title);
        }
        return false;
    }

服务端接收代码

/**
     * 接受App发送的通知内容
     * @param content   通知内容json, {"title": "标题", "content": "内容", "identifier": "app端标识", "orderid": "app生成的唯一订单号", "gateway": "wxpay或alipay"}
     * @param sign      签名,签名方式按照content对应的key1=vaule1&key2=value2...&SECKEY计算md5,key的顺序按字母表的顺序
     * @return
     */
    @RequestMapping(value = "/c/post/notification", method = { RequestMethod.POST })
    @ResponseBody
    public String receiveAppNotification(@RequestBody Map<String, Object> content, String sign) {
        logger.debug("请求参数,content=>{}, sign=>{}", JSON.toJSONString(content), sign);
        if (StringUtils.isBlank(sign) || CollectionUtils.isEmpty(content)) {
            return APIUtil.getReturn(APIConst.PARAM_ERROR);
        }
        //再次验证字段
        String contenttext = (String) content.get("content");
        String identifier = (String) content.get("identifier");
        String orderid = (String) content.get("orderid");
        String gateway = (String) content.get("gateway");
        if (StringUtils.isAnyBlank(contenttext, identifier, orderid, gateway) || !ImmutableList.of("alipay",
                "wxpay").contains(gateway)) {
            return APIUtil.getReturn(APIConst.PARAM_ERROR);
        }
        //读取金额(单位元)
        Pattern pattern = Pattern.compile("([1-9]\\d*\\.\\d*|0\\.\\d*[1-9]\\d*)");
        Matcher matcher = pattern.matcher(contenttext);
        if (!matcher.find()) {
            return APIUtil.getReturn(APIConst.PARAM_ERROR);
        }
        String amountStr = matcher.group(1);
        logger.debug("解析的金额:{}", amountStr);
        BigDecimal amount = null;
        try {
            amount = new BigDecimal(amountStr);
        } catch (NumberFormatException e) {
            logger.error("金额格式错误: {}", amountStr);
            return APIUtil.getReturn(APIConst.PARAM_ERROR);
        }

        //验证签名
        TreeMap<String, Object> paramMap = new TreeMap<>(content);
        Iterator<Map.Entry<String, Object>> it = paramMap.entrySet().iterator();
        StringBuilder sb = new StringBuilder();
        while (it.hasNext()) {
            Map.Entry<String, Object> entry = it.next();
            sb.append(entry.getKey());
            sb.append("=");
            sb.append(entry.getValue());
            sb.append("&");
        }
        sb.append(SIGN_KEY);
        //计算签名
        String calcSign = MD5Util.MD5Encode(sb.toString(), "UTF-8");
        if (!calcSign.equalsIgnoreCase(sign)) {
            return APIUtil.getReturn(1, "签名错误");
        }

        //查询订单号是否已经存在
        boolean exist = orderService.checkOrderExist(orderid);
        if (exist) {
            logger.error("订单号:{}已存在", orderid);
            return APIUtil.getReturn(1, "订单号已存在");
        }

        //订单写入数据库
        String account = "";
        if (gateway.equals("wxpay")) {
            account = "W" + identifier;
        } else if (gateway.equals("alipay")) {
            account = "A" + identifier;
        }
        MqOrder order = new MqOrder();
        order.setAccount(account);
        order.setAmount(amount);
        order.setGateway(gateway);
        order.setOrderId(orderid);
        order.setStatus(0);
        order.setNotifyCount(0);
        order.setCreateTime(new Date());
        orderService.save(order);

        return APIUtil.getReturn(APIConst.OK);
    }

 欢迎学习交流

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值