[微信小程序]微信支付实现

官方微信支付文档

java 后台微信小程序统一下单支付、以及二次签名(亲测可用)

准备工作
  • 调用前需在小程序微信公众平台 -功能-微信支付入口申请接入微信支付

    • 微信商户号mch_id
    • API密钥api_secret
  • 准备一个支付界面/payment

  • 选择支付接口版本。v3和旧版本v2的鉴权方式和请求参数都有很大区别,但最重要的是,截止到2020.12.31,v2版本提供更多接口功能,包括退款等。因此下面介绍的是v2版本的微信支付接口。

  • v2版本的demo下载地址:SDK与DEMO下载和文档目录:业务流程

  • 需要的pom依赖:

    <dependency>
        <groupId>xpp3</groupId>
        <artifactId>xpp3_min</artifactId>
        <version>1.1.4c</version>
    </dependency>
    <dependency>
        <groupId>com.thoughtworks.xstream</groupId>
        <artifactId>xstream</artifactId>
        <version>1.4.10</version>
    </dependency>
    
步骤1:签名,IP等业务无关参数的生成
  • 接口中包含一些与业务本身无关的参数,这些参数的生成过程是比较复杂的,很多可以引用DEMO里的方法。在后续的代码中会展示如何使用DEMO的工具类。

  • 随机字符串nonceStr:只要是32位以内的,由数字和字母组成的随机字符串即可。

    java中随机生成字符串的方法(三种)

  • 签名sign:官方的签名算法文档。

    • 将发送的数据中除去签名字段参数值非空的参数名按照ASCII码从小到大字典序排序,拼接成key1=value1&key2=value2的字符串stringA
    • 在stringA的最后拼接上&key=+API密钥api_secret,得到stringTemp
    • 对stringTemp进行MD5运算,得到signMD5
    • 将signMD5所有字符大写,得到最终sign值
  • 机器IPspbill_create_ip:直接人工填写是最快的,当然动态获取也可以(注意反向代理、局域网等)

  • 时间戳timestamp:注意服务器时区是否正确

步骤2:统一下单,获取预支付ID

第一步是要创建预支付交易单

  • 在后端创建一个生成预支付交易单的接口,如Res createWxOrder(Req entity)

    • entity的结构至少包含:

      {
          orderId:"1",
          openid:"2"
      }
      
    • 接口内的实现,使用官方的DEMO方法会变得异常简单:

      @Override
      public Map<String,Object> createWxOrder(WxOrderParam entity) throws Exception {
          String orderURL="https://api.mch.weixin.qq.com/pay/unifiedorder";
          Map<String,String> order=new HashMap<>();
          order.put("appid",appId);
          order.put("mch_id",merchantId);
          order.put("nonce_str",WXPayUtil.generateNonceStr());
          order.put("body","xxxx-xx");
          order.put("out_trade_no",entity.getOrderId());
          order.put("total_fee","1");
          order.put("spbill_create_ip", getLocalIp());
          order.put("notify_url","nonono");//onnononono 回调地址
          order.put("trade_type","JSAPI");
          //调用微信支付api接口(带有签名){统一下单}
          String orderXml=WXPayUtil.generateSignedXml(order,apiSecret);
          String resXml=HttpUtil.postWithXml(orderURL,orderXml,"utf-8");
      
          //接收微信支付接口{统一下单}的返回数据
          XStream xs = new XStream();
          XStream.setupDefaultSecurity(xs);
          xs.allowTypes(new Class[]{WxPayOrderInfoRes.class});
          xs.alias("xml", WxPayOrderInfoRes.class);
          WxPayOrderInfoRes returnInfo = (WxPayOrderInfoRes)xs.fromXML(resXml);
          //二次签名
          ....
      }
      
  • 微信支付后台接收到这个预付单后,会返回一个非常重要的预支付交易会话标识prepay_id,用于后续接口调用,2小时内过期。

  • 如果发生异常,则会返回异常码等。

步骤3:二次签名,用于wx.requestPayment鉴权
  • 上面我们拿到了标识还不能急着返回,我们需要结合返回的数据再次生成签名,然后才能返回给小程序。因为wx.requestPayment需要后端提供几个重要的参数用于接口鉴权。

  • 生成签名的方法与之前类似:

    @Override
    public Map<String,Object> createWxOrder(WxOrderParam entity) throws Exception {
        //统一下单
        //...
        //二次签名
        Map<String,Object> payInfo = new HashMap<>();
        if ("SUCCESS".equals(returnInfo.getReturn_code()) && "SUCCESS".equals(returnInfo.getResult_code())) {
            long time = System.currentTimeMillis()/1000;
            //生成签名(官方给出来的签名方法)
            Map<String,String> map2 = new HashMap<>();
            map2.put("appId", appId);
            map2.put("timeStamp", String.valueOf(time));
            //这边的随机字符串必须是第一次生成sign时,微信返回的随机字符串,不然小程序支付时会报签名错误
            map2.put("nonceStr", returnInfo.getNonce_str());
            map2.put("package", "prepay_id=" + returnInfo.getPrepay_id());
            map2.put("signType", "MD5");
    
            String sign2 = WXPayUtil.generateSignature(map2, apiSecret);
            //无效的签名方法
            //String sign1 = Signature.getSign(signInfo);
            payInfo.put("timeStamp", String.valueOf(time));
            payInfo.put("nonceStr", returnInfo.getNonce_str());
            payInfo.put("prepay_id","prepay_id=" + returnInfo.getPrepay_id());
            payInfo.put("signType", "MD5");
            payInfo.put("paySign", sign2);
        }
        else
        {
            payInfo.put("errorBody", resXml);
        }
        return payInfo;
    }
    
步骤4:小程序调出支付弹窗,输入密码支付订单
  • 小程序端应该先同步调用服务端的生成订单,然后等到那些鉴权参数返回后,调用wx.requestPayment发起支付。

    wx.request({
        url: app.globalData.ServerHost+'dealer/mina/createWxOrder',
        data: {
            openid:app.globalData.openid,
            orderId:"icytest"+Math.random().toString(16).substr(2, 8),
        },
        header: {'content-type':'application/json'},
        method: 'POST',
        dataType: 'json',
        success: (result)=>{
            console.log('统一下单:',result)
            wx.requestPayment({
                timeStamp: result.data.timeStamp,
                nonceStr: result.data.nonceStr,
                package: result.data.prepay_id,
                signType:result.data.signType,
                paySign: result.data.paySign,
                success: (result)=>{
                    console.log('发起微信支付:',result)
                },
                fail: ()=>{},
                complete: ()=>{}
            });
        },
        fail: ()=>{},
        complete: ()=>{}
    });
    
步骤5:支付完成,服务端接受回调通知
  • 微信在处理完支付订单后,会调用先前notify_url传递的接口。

  • 这个回调接口我们需要:

    • 接收xml报文,并验证是否返回SUCCESS。
    • 验证签名及订单金额和实付金额。
    • 处理支付完毕后的业务逻辑
    • 通知微信平台已经成功收到
  • 可能会多次重复通知,要做好能处理重复消息的准备。

  • 使用流数据的方式来传输。

    @Override
    public void notifyWxPayment(HttpServletRequest request, HttpServletResponse response) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(request.getInputStream()));
        String line;
        StringBuilder sb = new StringBuilder();
        while((line = br.readLine()) != null){
            sb.append(line);
        }
        br.close();
        //sb为微信返回的xml
        String notityXml = sb.toString();
        String resXml = "";
        System.out.println("{微信支付完成回调}接收到的报文:" + notityXml);
    
        Map<String, String> map = WXPayUtil.xmlToMap(notityXml);
    
        String returnCode = map.get("return_code");
        if("SUCCESS".equals(returnCode)){
            //验证签名是否正确
            Map<String, String> validParams = PayUtil.paraFilter(map);  //回调验签时需要去除sign和空值参数
            //把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串
            String validStr = PayUtil.createLinkString(validParams);
            //拼装生成服务器端验证的签名
            String sign = PayUtil.sign(validStr, Configure.getKey(), "utf-8").toUpperCase();
            // 当第一次回调成功了,那么我们就不再执行逻辑了
            
            //根据微信官网的介绍,此处不仅对回调的参数进行验签,还需要对返回的金额与系统订单的金额进行比对等
            if(sign.equals(map.get("sign"))){
    			System.out.println("我执行我的业务逻辑");
                //通知微信服务器已经支付成功
                resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>"
                    + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml>";
            } else {
                System.out.println("微信支付回调失败!签名不一致");
            }
        }else{
            resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>"
                + "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml>";
        }
    
        BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());
        out.write(resXml.getBytes());
        out.flush();
        out.close();
    }
    
步骤6:小程序获取支付成功通知解决方案
坑:签名错误

微信支付签名错误

  • 签名错误的时候可以先去微信支付接口签名校验工具校验一下。可能是签名算法错误了。
  • 有可能是body里包含中文,没有处理好。排查这个的方法是不用中文body就能通过签名验证。解决办法是:
httpPost.setHeader("Content-type", "text/xml;charset=utf-8");
  • 有可能是API密钥api_secret错误,需要重置。

MINE MIND系列将在我的GitHub上实时更新,同时精选部分汇总于CSDN专栏
GitHub仓库:https://github.com/IcyLeaves/MINE-MIND
CSDN专栏:https://blog.csdn.net/qq_37398834/category_10975647.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值