原创 Dr Hydra 码农参上 2020-11-22 11:00

收录于合集#微信开发技术3个

最近在工作中接入了一下微信小程序支付的功能,虽然说官方文档已经比较详细了,但在使用过程中还是踩了不少的坑,整理了一下大体的流程和代码分享出来。在开始使用小程序支付功能前,需要做好以下的准备工作:

  • 申请微信小程序,配置小程序id及秘钥

  • 申请用于支付的微信商户平台账号,配置商户号id及商户平台秘钥,并绑定小程序与该商户号

  • 后端服务在正式环境下需要https域名,调试模式可以不需要

先引用一张小程序支付官方说明的流程图,可以看出小程序支付的主要逻辑集中在后端,前端只需要携带参数请求后端接口,然后根据后端接口返回的数据在前端唤起微信支付即可。

按照上面流程图中商户业务系统和微信支付系统主要交互步骤,对流程进行拆解说明。

1

获取用户openId

小程序前端调用wx.login()获取登录凭证code,后端调用接口获取用户的openid和session_key。注意这里在发起请求的时候需要携带小程序的appId和appSecret。

public OpenIdInfo code2Openid(String code){    String url = "https://api.weixin.qq.com/sns/jscode2session";    String param = "appid=" + mpCommonProperty.getAppid() +            "&secret=" + mpCommonProperty.getAppsecret() +            "&js_code=" + code +            "&grant_type=authorization_code";
    String rs = HttpUtils.sendGet(url, param);    JSONObject json = JSONObject.parseObject(rs);
    if (json.get("errcode") == null) {        String openid = json.getString("openid");        String sessionKey = json.getString("session_key");        OpenIdInfo openIdInfo = OpenIdInfo.builder()                .openId(openid).sessionKey(sessionKey).build();        return openIdInfo;    }else {        log.error("get openid error");        return null;    }}

需要注意每次调用接口都会刷新session_key的值,使之前的session_key失效,其他操作诸如解析用户手机号时会用到这个秘钥,为了避免该情况可以将用户的openid存储在业务系统的用户体系中。

2

调用支付统一下单

微信统一下单接口要求传递参数的形式为xml报文,因此需要先对参数进行拼接,这里仅列出了能够唤起小程序支付所需要的最小参数范围,更多的参数列表可以查看官方文档。

public String generateUniPayXml(UnifiedParam unifiedParam){    int money = (int) Math.ceil(unifiedParam.getTotalMoney() * 100);   //转换为分,向上取整
    Map<String,String> map=new HashMap<>();    map.put("appid", mpCommonProperty.getAppid()); //小程序id    map.put("mch_id", mpCommonProperty.getMuchId());  //商户号    map.put("nonce_str",UUID.randomUUID().toString().replaceAll("-",""));   //随机字符串    map.put("body", unifiedParam.getPayBody());    //商品描述    map.put("out_trade_no", unifiedParam.getOrderNumber());    //商户订单号    map.put("total_fee",String.valueOf(money)); //标价金额, 订单总金额单位为分    map.put("spbill_create_ip",IpUtils.getInternetIp());   //终端IP    map.put("notify_url", mpCommonProperty.getServerDomain()+ "/pay/fallback");//通知地址    map.put("trade_type","JSAPI");//交易类型    map.put("openid", unifiedParam.getOpenid());//用户标识,trade_type=JSAPI 时此参数必传
    String sign = signCommon(map);    map.put("sign",sign);    //生成签名
    String xml = XmlUtil.generateXmlFromMap(map);    log.info(xml);    return xml;}

对其中几个参数进行说明:

out_trade_no:商户订单号,在我们的后台使用某种规则生成,不能重复

total_fee:订单总金额,需要注意单位为分,需要转换

body:商品描述

notify_url:支付结果的回调接口地址,使用会在后面介绍

sign:签名,需要按照微信的规则生成,算法规则为去除值为空的元素,参数名ASCII字典序排序进行拼接,拼接API密钥,使用Md5进行加密:

public String signCommon(Map<String,String> map){    Set<String> emptySet=new HashSet<>();    map.forEach((K,V)->{        if (StringUtils.isEmpty(map.get(K))){            emptySet.add(K);        }    });    for (String key : emptySet) {        map.remove(key);    }
    Set<String> keySet =  map.keySet();    String[] array = keySet.toArray(new String[keySet.size()]);    Arrays.sort(array);
    StringBuffer sb=new StringBuffer();    for (String key : array) {        sb.append(key+"="+map.get(key)+"&");    }    sb.append("key=").append(mpCommonProperty.getMuchSecret());    System.out.println(sb.toString());    String md5Sign = Md5Utils.hash(sb.toString());    return md5Sign;}

以上步骤完成后,对外暴露的统一下单接口如下:

public Map<String, String> unifiedOrder(UnifiedParam unifiedParam){    String xml = mpPayUtil.generateUniPayXml(unifiedParam);    String url= "https://api.mch.weixin.qq.com/pay/unifiedorder";    String xmlResult  = HttpUtils.sendPost(url, xml);
    //发送请求成功    if (xmlResult.indexOf("SUCCESS")!=-1){        Map<String, String> parseXmlToMap = XmlUtil.parseXmlToMap(xmlResult);        return parseXmlToMap;    }else{        throw new RuntimeException("统一支付错误");    }}

在调用后,会收到同步返回结果为一段xml报文,将其解析成Map后可供下一阶段使用,同步接口的返回值及错误码可以参考官方文档。

3

二次签名

在调用统一下单接口并收到微信的同步返回结果后,需要对其进行二次签名,需要进行签名的参数包括appId、timeStamp、nonceStr、package、signType。

public PrepayInfo secondSign(Map<String, String> unifiedOrderMap){    Map<String,String> map=new HashMap<>();    map.put("appId", mpCommonProperty.getAppid());    map.put("timeStamp",String.valueOf(System.currentTimeMillis()/1000));    map.put("nonceStr",unifiedOrderMap.get("nonce_str"));    map.put("package","prepay_id="+unifiedOrderMap.get("prepay_id"));    map.put("signType",WechatConstants.signType);
    String sign = mpPayUtil.signCommon(map);    map.put("paySign",sign);    map.put("prePackage",unifiedOrderMap.get("prepay_id"));
    PrepayInfo prepayInfo =new PrepayInfo();    BeanUtil.copyProperties(map, prepayInfo);    return prepayInfo;}

二次签名完成后,将timeStamp、nonceStr、package、signType、paySign返回给前端,这里为了方便封装了一个对象用于返回,前端在收到参数后唤起微信支付。

4

接收支付通知

在前面介绍的统一下单的参数中,传入了商户后端的回调地址,在支付完成后,微信会向这个调用这个回调接口,通知支付结果。

@PostMapping("fallback")public void fallback(HttpServletRequest request,HttpServletResponse response) throws IOException {    StringBuilder sb = new StringBuilder();    BufferedReader reader = null;    try (InputStream inputStream = request.getInputStream()) {        reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")));        String line = "";        while ((line = reader.readLine()) != null) {            sb.append(line);        }    } catch (IOException e) {        log.error("getBodyString错误");    } finally {        if (reader != null) {            try {                reader.close();            } catch (IOException e) {                log.error(ExceptionUtils.getMessage(e));            }        }    }        String notifyXml=sb.toString();    Map<String, String> params = XmlUtil.parseXmlToMap(notifyXml);    boolean result = false;    String resultXml;    if ("SUCCESS".equals(params.get("return_code"))) {//通信成功,非交易标识        //验证签名        if (WechatPayUtil.validSignature(notifyXml)) {            //执行业务逻辑            result=true;        }else{            log.error("微信支付成功回调验证签名错误!");        }    }else {        log.error("Fallback回调结果 : "+params.get("return_msg"));    }        if (result){        resultXml="<xml><return_code><![CDATA[SUCCESS]]></return_code>" +                "<return_msg><![CDATA[OK]]></return_msg></xml>";    }else {        resultXml="<xml><return_code><![CDATA[FAIL]]></return_code>"                + "<return_msg><![CDATA[ERROR]]></return_msg></xml> ";    }               ServletOutputStream outputStream = response.getOutputStream();    outputStream.println(result);    outputStream.close();}

在接收到返回的报文后,需要用之前同样的签名算法,验证返回报文的真实性,并在验证真实性后再执行之后的业务逻辑,防止数据泄漏导致出现的虚假通知,造成资金损失。

微信在调用回调接口时,如果收到我们业务系统的应答不符合规范或超时,会判定本次通知失败,重新发送多次通知。在通知一直不成功的情况下,按照官方文档的说明,总计在24h4m内会调用15次回调接口。因此一定要按照规定返回成功接收的报文,从一定程度上也能降低系统的负载。

在测试中发现,不能使用直接返回String字符串的方式进行结果的返回,仍然会一直发起回调,必须使用HttpServletResponse写入返回。即使这么做了,还是建议大家在回调接口内部处理业务前再做一下幂等性的处理,防止多次执行回调逻辑造成业务系统的数据混乱。

微信小程序支付流程详解相关推荐

  1. 微信小程序退款流程详解

    原创 Dr Hydra 码农参上 2020-11-29 11:00 收录于合集#微信开发技术3个 在上一篇中我们介绍了微信小程序的支付流程,这一篇接着讲一下小程序的退款流程,首先看一下官方给出的介绍: ...

  2. 【迷宫】地下迷宫游戏-微信小程序开发流程详解

    可曾记得,小时候上学路边买的透明铅笔盒,里面内嵌了一个小球,它用重力可从起点滚动到终点,对小朋友来说是感觉有趣的,在这个游戏的基础上,弄一款微信小程序的迷宫探索游戏试试,在不同关卡的迷宫中解开机关与谜 ...

  3. 服务端微信小程序支付/退款详解

    一.小程序支付 参考小程序支付开发文档:https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=7_4&index=3 账 ...

  4. Python-Flask微信小程序登录流程详解及后台实现

    文章目录 登录流程图及个人理解 登录接口源码 登录流程图及个人理解 1.前端将由wx.login()方法获取到的用户临时登录凭证code(只能使用一次)传给后台服务器(即登录接口) 2.后台利用微信小 ...

  5. 【数独】数独游戏-微信小程序开发流程详解

    有没有玩过数独游戏呢,听说,它是一个能训练大脑思维的棋盘类游戏,游戏规则很简单,通过小程序来实现很容易,非常适合对数独游戏逻辑感兴趣的同学,选择它开发入门吧. 准备 会使用微信开发者工具, 有Java ...

  6. 【答题】在线答卷-答题系统的微信小程序开发流程详解

    用死记硬背的方法学习的学生,面对桌上堆积成厚厚的书本,是否感觉鸭梨山大呢,想着教育却面临着学习成本不小问题,是否感觉各种不便呢,如果对编程代码有感兴趣,不妨试试做一个自己的在线答题系统,这里可以用微信 ...

  7. 【拼图】拼图游戏-微信小程序开发流程详解

    还记得小时候玩过的经典拼图游戏吗,上小学时,在路边摊用买个玩具,是一个正方形盒子形状,里面装的是图片分割成的很多块,还差一块,怎么描述好呢,和魔方玩具差不多,有没有听说叫二维的魔方,这里用小程序把它实 ...

  8. 【飞行棋】多人游戏-微信小程序开发流程详解

    可曾记得小时候玩过的飞行棋游戏,是90后的都有玩过吧,现在重温一下,这是一个可以二到四个人参与的游戏,通过投骰子走棋,一开始靠运气,后面还靠自己选择,谁抢占先机才能赢,还可以和小伙伴们一起玩,狭路相逢 ...

  9. 【跳棋】跳棋游戏-多人游戏-微信小程序开发流程详解

    看到跳棋游戏,一个2到6人可一起玩的游戏,于是联想起,自己上小学时候陪同学们玩过的弹珠游戏,是不是跟跳棋游戏很像呢,看了跳棋玩法,有兴趣就研究,这里把跳棋游戏给弄出来了,想知道地图怎么画,对此感兴趣的 ...

最新文章

  1. 病人还能生存多久?现在AI能给出更精准的预测
  2. MySQL(1)数据库介绍,配置MySQL的tab补全
  3. linux安装nodejs一键脚本,ubuntu16.04部署nodejs+vue框架脚本
  4. C++ priority_queue对链表节点建立小根堆
  5. 求最长上升子序列(Lis模板)
  6. 操作系统---页面置换算法
  7. html css 样式中100%width 仍有白边解决办法
  8. JQ实现王者荣耀手风琴效果
  9. 用Python绘制了若干张词云图,惊艳了所有人
  10. 三容水箱液位控制系统_基于Labview软件编程的三容水箱液位控制系统
  11. 10 个深恶痛绝的 Java 异常
  12. vscode快速生成HTML模板
  13. 如何查询期刊并免费下载论文,纬度学术利用
  14. 今天安利几个实用的APP给你
  15. 爱创课堂每日一题-Javascript垃圾回收方法?
  16. [转载]微信企业号:企业客户的移动应用入口
  17. Mysql语法系列—show full processlist介绍
  18. 甘肃非煤矿山电子封条 智慧矿山 opencv
  19. 视频合成软件,怎么把几个视频合在一起
  20. metasploit基础命令操作

热门文章

  1. Android APP Camera2应用(04)录像保存视频流程
  2. [转]2005年中国信息产业十大丑闻点评
  3. css层叠样式表基础学习笔记--第一章 css简介及引入
  4. 一文读懂雨量传感器的前世今生
  5. 一个简单的Java应用程序
  6. redis批处理操作:基于hiredis实现
  7. win10运行python没有硬编码器_实测Win10下加载github上的Python版本斗图神器
  8. 如何使用打单发货软件管理订单,提升50%工作效率
  9. 基金管理 touzi
  10. JAVA三种多线程实现方法和应用总结