微信小程序-統一下單、微信支付(Java後臺)

一、首先分享 微信統一下單接口: javascript

      https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1 php

        微信接口 簽名 對比網址:html

      https://pay.weixin.qq.com/wiki/tools/signverify/前端

        微信小程序 微信支付 網址:java

      https://mp.weixin.qq.com/debug/wxadoc/dev/api/api-pay.html#wxrequestpaymentobject算法

二、微信小程序端 代碼示例:sql

payment:function(event){
    var that = this;
    console.log('去支付按鈕點擊事件')
    wx.request({
      url: 'https://192.168.0.666:8080/matouwang/wechat/wechatAppletGolf/createUnifiedOrder',
      method: 'POST', // OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, CONNECT
      // 當method 爲POST 時 設置如下 的header 
      header: { 'content-type': 'application/x-www-form-urlencoded' },
      data: {
        amount: teamMoney,
        openid: openId
      },
      success: function (res) {
        if (res.data.prepayId != ''){
          console.log('微信統一下單接口調用成功 數據包:' + res.data.prepayId);
          console.log('微信統一下單接口調用成功 訂單號:' + res.data.outTradeNo);
          console.log('調用微信支付接口以前先生成簽名')
          //保存訂單號信息
          var outTradeNo = res.data.outTradeNo;
          wx.request({
            url: 'http://192.168.8.50:8080/matouwang/wechat/wechatAppletGolf/generateSignature',
            method: 'POST', // OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, CONNECT
            // 當method 爲POST 時 設置如下 的header 
            header: { 'content-type': 'application/x-www-form-urlencoded' },
            data: {
              prepayId: res.data.prepayId
            },
            success: function (paryResult) {
              if (paryResult.data.sign != '') {
                console.log('微信支付接口以前先生成簽名成功')
                console.log('簽名:' + paryResult.data.sign)
                console.log('隨機串:' + paryResult.data.nonceStr)
                console.log('時間戳:' + paryResult.data.timeStamp)
                //這個applyId必定要大寫 並且簽名的參數和調用方法的參數值必定要統一
                wx.requestPayment({
                  'appId': '',
                  'timeStamp': paryResult.data.timeStamp,
                  'nonceStr': paryResult.data.nonceStr,
                  'package': paryResult.data.package,
                  'signType': 'MD5',
                  'paySign': paryResult.data.sign,
                  'success': function (paymentRes) {
                    console.log(paymentRes)
                    that.setData({
                      notPay: true,
                      paySuccess: false,
                      teamNotPay: true,
                      button:true,
                      outTradeNo: outTradeNo,
                      payDate:new Date()
                    })
                  },
                  'fail': function (error) {
                    console.log(error)
                  }
                })
              } else {
                console.log('微信支付接口以前先生成簽名失敗')
              }
            }
          })
        }
      }
    });
  },

三、分享微信 統一下單 後臺代碼示例:小程序

@Override
    public JSONObject createUnifiedOrder(HttpServletRequest request,HttpServletResponse response) {
        logger.info("微信 統一下單 接口調用");
        //設置最終返回對象
        JSONObject resultJson = new JSONObject();
        //建立條件
        Criteria criteria = new Criteria();
    
        //接受參數(金額)
        String amount = request.getParameter("amount");
        //接受參數(openid)
        String openid = request.getParameter("openid");
        //接口調用總金額單位爲分換算一下(測試金額改爲1,單位爲分則是0.01,根據本身業務場景判斷是轉換成float類型仍是int類型)
        //String amountFen = Integer.valueOf((Integer.parseInt(amount)*100)).toString();
        //String amountFen = Float.valueOf((Float.parseFloat(amount)*100)).toString();
        String amountFen = "1";
        //建立hashmap(用戶得到簽名)
        SortedMap<String, String> paraMap = new TreeMap<String, String>();
        //設置body變量 (支付成功顯示在微信支付 商品詳情中)
        String body = "啦啦啦測試";
        //設置隨機字符串
        String nonceStr = Utils.getUUIDString().replaceAll("-", "");
        //設置商戶訂單號
        String outTradeNo = Utils.getUUIDString().replaceAll("-", "");
        
        
        //設置請求參數(小程序ID)
        paraMap.put("appid", APPLYID);
        //設置請求參數(商戶號)
        paraMap.put("mch_id", MCHID);
        //設置請求參數(隨機字符串)
        paraMap.put("nonce_str", nonceStr);
        //設置請求參數(商品描述)
        paraMap.put("body", body);
        //設置請求參數(商戶訂單號)
        paraMap.put("out_trade_no", outTradeNo);
        //設置請求參數(總金額)
        paraMap.put("total_fee", amountFen);
        //設置請求參數(終端IP)
        paraMap.put("spbill_create_ip", WebUtils.getIpAddress(request, response));
        //設置請求參數(通知地址)
        paraMap.put("notify_url", WebUtils.getBasePath()+"wechat/wechatAppletGolf/payCallback");
        //設置請求參數(交易類型)
        paraMap.put("trade_type", "JSAPI");
        //設置請求參數(openid)(在接口文檔中 該參數 是否必填項 可是必定要注意 若是交易類型設置成'JSAPI'則必須傳入openid)
        paraMap.put("openid", openid);
        //調用邏輯傳入參數按照字段名的 ASCII 碼從小到大排序(字典序)
        String stringA = formatUrlMap(paraMap, false, false);
        //第二步,在stringA最後拼接上key獲得stringSignTemp字符串,並對stringSignTemp進行MD5運算,再將獲得的字符串全部字符轉換爲大寫,獲得sign值signValue。(簽名)
        String sign = MD5Util.MD5(stringA+"&key="+KEY).toUpperCase();
        //將參數 編寫XML格式
        StringBuffer paramBuffer = new StringBuffer();
        paramBuffer.append("<xml>");
        paramBuffer.append("<appid>"+APPLYID+"</appid>");
        paramBuffer.append("<mch_id>"+MCHID+"</mch_id>");
        paramBuffer.append("<nonce_str>"+paraMap.get("nonce_str")+"</nonce_str>");
        paramBuffer.append("<sign>"+sign+"</sign>");
        paramBuffer.append("<body>"+body+"</body>");
        paramBuffer.append("<out_trade_no>"+paraMap.get("out_trade_no")+"</out_trade_no>");
        paramBuffer.append("<total_fee>"+paraMap.get("total_fee")+"</total_fee>");
        paramBuffer.append("<spbill_create_ip>"+paraMap.get("spbill_create_ip")+"</spbill_create_ip>");
        paramBuffer.append("<notify_url>"+paraMap.get("notify_url")+"</notify_url>");
        paramBuffer.append("<trade_type>"+paraMap.get("trade_type")+"</trade_type>");
        paramBuffer.append("<openid>"+paraMap.get("openid")+"</openid>");
        paramBuffer.append("</xml>");
        
        try {
            //發送請求(POST)(得到數據包ID)(這有個注意的地方 若是不轉碼成ISO8859-1則會告訴你body不是UTF8編碼 就算你改爲UTF8編碼也同樣很差使 因此修改爲ISO8859-1)
            Map<String,String> map = doXMLParse(getRemotePortData(URL, new String(paramBuffer.toString().getBytes(), "ISO8859-1")));
            //應該建立 支付表數據
            if(map!=null){
                //清空
                criteria.clear();
                //設置openId條件
                criteria.put("openId", openid);
                //獲取數據
                List<WechatAppletGolfPayInfo> payInfoList = appletGolfPayInfoMapper.selectByExample(criteria);
                //若是等於空 則證實是第一次支付
                if(CollectionUtils.isEmpty(payInfoList)){
                    //建立支付信息對象
                    WechatAppletGolfPayInfo appletGolfPayInfo = new  WechatAppletGolfPayInfo();
                    //設置主鍵
                    appletGolfPayInfo.setPayId(outTradeNo);
                    //設置openid
                    appletGolfPayInfo.setOpenId(openid);
                    //設置金額
                    appletGolfPayInfo.setAmount(Long.valueOf(amount));
                    //設置支付狀態
                    appletGolfPayInfo.setPayStatus("0");
                    //插入Dao
                    int sqlRow = appletGolfPayInfoMapper.insert(appletGolfPayInfo);
                    //判斷
                    if(sqlRow == 1){
                        logger.info("微信 統一下單 接口調用成功 而且新增支付信息成功");
                        resultJson.put("prepayId", map.get("prepay_id"));
                        resultJson.put("outTradeNo", paraMap.get("out_trade_no"));
                        return resultJson;
                    }
                }else{
                    //判斷 是否等於一條
                    if(payInfoList.size() == 1){
                        //獲取 須要更新數據
                        WechatAppletGolfPayInfo wechatAppletGolfPayInfo = payInfoList.get(0);
                        //更新 該條的 金額
                        wechatAppletGolfPayInfo.setAmount(Long.valueOf(amount));
                        //更新Dao
                        int sqlRow = appletGolfPayInfoMapper.updateByPrimaryKey(wechatAppletGolfPayInfo);
                        //判斷
                        if(sqlRow == 1){
                            logger.info("微信 統一下單 接口調用成功 修改支付信息成功");
                            resultJson.put("prepayId", map.get("prepay_id"));
                            resultJson.put("outTradeNo", paraMap.get("out_trade_no"));
                            return resultJson;
                        }
                    }
                }
            }
            //將 數據包ID 返回
            
            System.out.println(map);
        } catch (UnsupportedEncodingException e) {
            logger.info("微信 統一下單 異常:"+e.getMessage());
            e.printStackTrace();
        } catch (Exception e) {
            logger.info("微信 統一下單 異常:"+e.getMessage());
            e.printStackTrace();
        }
        logger.info("微信 統一下單 失敗");
        return resultJson;
    }

四、生成 微信支付 簽名後臺 代碼示例:微信小程序

@Override
    public JSONObject generateSignature(HttpServletRequest request,
            HttpServletResponse response) {
        logger.info("微信 支付接口生成簽名 方法開始");
        //實例化返回對象
        JSONObject resultJson = new JSONObject();
        
        //得到參數(微信統一下單接口生成的prepay_id )
        String prepayId = request.getParameter("prepayId");
        //建立 時間戳
        String timeStamp = Long.valueOf(System.currentTimeMillis()).toString();
        //建立 隨機串
        String nonceStr = Utils.getUUIDString().replaceAll("-", "");
        //建立 MD5
        String signType = "MD5";
        
        //建立hashmap(用戶得到簽名)
        SortedMap<String, String> paraMap = new TreeMap<String, String>();
        //設置(小程序ID)(這塊必定要是大寫)
        paraMap.put("appId", APPLYID);
        //設置(時間戳)
        paraMap.put("timeStamp", timeStamp);
        //設置(隨機串)
        paraMap.put("nonceStr", nonceStr);
        //設置(數據包)
        paraMap.put("package", "prepay_id="+prepayId);
        //設置(簽名方式)
        paraMap.put("signType", signType);
        
        
        //調用邏輯傳入參數按照字段名的 ASCII 碼從小到大排序(字典序)
        String stringA = formatUrlMap(paraMap, false, false);
        //第二步,在stringA最後拼接上key獲得stringSignTemp字符串,並對stringSignTemp進行MD5運算,再將獲得的字符串全部字符轉換爲大寫,獲得sign值signValue。(簽名)
        String sign = MD5Util.MD5(stringA+"&key="+KEY).toUpperCase();
        
        if(StringUtils.isNotBlank(sign)){
            //返回簽名信息
            resultJson.put("sign", sign);
            //返回隨機串(這個隨機串是新建立的)
            resultJson.put("nonceStr", nonceStr);
            //返回時間戳
            resultJson.put("timeStamp", timeStamp);
            //返回數據包
            resultJson.put("package", "prepay_id="+prepayId);
            
            logger.info("微信 支付接口生成簽名 設置返回值");
        }
        logger.info("微信 支付接口生成簽名 方法結束");
        return resultJson;
    }

五、簽名算法 將key Value 字典排序 代碼示例:api

/** 
     *  
     * 方法用途: 對全部傳入參數按照字段名的 ASCII 碼從小到大排序(字典序),而且生成url參數串<br> 
     * 實現步驟: <br> 
     *  
     * @param paraMap   要排序的Map對象 
     * @param urlEncode   是否須要URLENCODE 
     * @param keyToLower    是否須要將Key轉換爲全小寫 
     *            true:key轉化成小寫,false:不轉化 
     * @return 
     */  
    private static String formatUrlMap(Map<String, String> paraMap, boolean urlEncode, boolean keyToLower){  
        String buff = "";  
        Map<String, String> tmpMap = paraMap;  
        try  
        {  
            List<Map.Entry<String, String>> infoIds = new ArrayList<Map.Entry<String, String>>(tmpMap.entrySet());  
            // 對全部傳入參數按照字段名的 ASCII 碼從小到大排序(字典序)  
            Collections.sort(infoIds, new Comparator<Map.Entry<String, String>>()  
            {  
                @Override  
                public int compare(Map.Entry<String, String> o1, Map.Entry<String, String> o2)  
                {  
                    return (o1.getKey()).toString().compareTo(o2.getKey());  
                }  
            });  
            // 構造URL 鍵值對的格式  
            StringBuilder buf = new StringBuilder();  
            for (Map.Entry<String, String> item : infoIds)  
            {  
                if (StringUtils.isNotBlank(item.getKey()))  
                {  
                    String key = item.getKey();  
                    String val = item.getValue();  
                    if (urlEncode)  
                    {  
                        val = URLEncoder.encode(val, "utf-8");  
                    }  
                    if (keyToLower)  
                    {  
                        buf.append(key.toLowerCase() + "=" + val);  
                    } else  
                    {  
                        buf.append(key + "=" + val);  
                    }  
                    buf.append("&");  
                }  
   
            }  
            buff = buf.toString();  
            if (buff.isEmpty() == false)  
            {  
                buff = buff.substring(0, buff.length() - 1);  
            }  
        } catch (Exception e)  
        {  
           return null;  
        }  
        return buff;  
    }

六、發送遠程請求 得到數據 代碼示例:

/**
     * 方法名: getRemotePortData
     * 描述: 發送遠程請求 得到代碼示例
     * 參數:  @param urls 訪問路徑
     * 參數:  @param param 訪問參數-字符串拼接格式, 例:port_d=10002&port_g=10007&country_a=
     * 版本號: v1.0   
     * 返回類型: String
    */
    private String getRemotePortData(String urls, String param){
        logger.info("港距查詢抓取數據----開始抓取外網港距數據");
        try {
            URL url = new URL(urls);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            // 設置鏈接超時時間
            conn.setConnectTimeout(30000);
            // 設置讀取超時時間
            conn.setReadTimeout(30000);
            conn.setRequestMethod("POST");
            if(StringUtil.isNotBlank(param)) {
                conn.setRequestProperty("Origin", "https://sirius.searates.com");// 主要參數
                conn.setRequestProperty("Referer", "https://sirius.searates.com/cn/port?A=ChIJP1j2OhRahjURNsllbOuKc3Y&D=567&G=16959&shipment=1&container=20st&weight=1&product=0&request=&weightcargo=1&");
                conn.setRequestProperty("X-Requested-With", "XMLHttpRequest");// 主要參數
            }
            // 須要輸出
            conn.setDoInput(true);
            // 須要輸入
            conn.setDoOutput(true);
            // 設置是否使用緩存
            conn.setUseCaches(false);
            // 設置請求屬性
            conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
            conn.setRequestProperty("Connection", "Keep-Alive");// 維持長鏈接
            conn.setRequestProperty("Charset", "UTF-8");
            
            if(StringUtil.isNotBlank(param)) {
                // 創建輸入流,向指向的URL傳入參數
                DataOutputStream dos=new DataOutputStream(conn.getOutputStream());
                dos.writeBytes(param);
                dos.flush();
                dos.close();
            }
            // 輸出返回結果
            InputStream input = conn.getInputStream();
            int resLen =0;
            byte[] res = new byte[1024];
            StringBuilder sb=new StringBuilder();
            while((resLen=input.read(res))!=-1){
                sb.append(new String(res, 0, resLen));
            }
            return sb.toString();
        } catch (MalformedURLException e) {
            e.printStackTrace();
            logger.info("港距查詢抓取數據----抓取外網港距數據發生異常:" + e.getMessage());
        } catch (IOException e) {
            e.printStackTrace();
            logger.info("港距查詢抓取數據----抓取外網港距數據發生異常:" + e.getMessage());
        }
        logger.info("港距查詢抓取數據----抓取外網港距數據失敗, 返回空字符串");
        return "";
    }

七、XML 轉換成 map 對象 代碼示例:

/**
     * 解析xml,返回第一級元素鍵值對。若是第一級元素有子節點,則此節點的值是子節點的xml數據。
     * @param strxml
     * @return
     * @throws JDOMException
     * @throws IOException
     */
    @SuppressWarnings("rawtypes")
    private Map<String,String> doXMLParse(String strxml) throws Exception {
        if(null == strxml || "".equals(strxml)) {
            return null;
        }
        
        Map<String,String> m = new HashMap<String,String>();
        InputStream in = String2Inputstream(strxml);
        SAXBuilder builder = new SAXBuilder();
        Document doc = builder.build(in);
        Element root = doc.getRootElement();
        List list = root.getChildren();
        Iterator it = list.iterator();
        while(it.hasNext()) {
            Element e = (Element) it.next();
            String k = e.getName();
            String v = "";
            List children = e.getChildren();
            if(children.isEmpty()) {
                v = e.getTextNormalize();
            } else {
                v = getChildrenText(children);
            }
            
            m.put(k, v);
        }
        
        //關閉流
        in.close();
        
        return m;
    }
    
    private  InputStream String2Inputstream(String str) {
        return new ByteArrayInputStream(str.getBytes());
    }
    
    /**
     * 獲取子結點的xml
     * @param children
     * @return String
     */
    @SuppressWarnings("rawtypes")
    private static String getChildrenText(List children) {
        StringBuffer sb = new StringBuffer();
        if(!children.isEmpty()) {
            Iterator it = children.iterator();
            while(it.hasNext()) {
                Element e = (Element) it.next();
                String name = e.getName();
                String value = e.getTextNormalize();
                List list = e.getChildren();
                sb.append("<" + name + ">");
                if(!list.isEmpty()) {
                    sb.append(getChildrenText(list));
                }
                sb.append(value);
                sb.append("</" + name + ">");
            }
        }
        
        return sb.toString();
    }

八、獲取本機IP

/**
     * @Title: getIpAddress
     * @Description: 獲取客戶端真實IP地址
     * @author yihj
     * @param @param request
     * @param @param response
     * @param @return    參數
     * @return String    返回類型
     * @throws
     */
    public static String getIpAddress(HttpServletRequest request) {
        // 避免反向代理不能獲取真實地址, 取X-Forwarded-For中第一個非unknown的有效IP字符串
        String ip = request.getHeader("x-forwarded-for");
       if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){
           ip = request.getHeader("Proxy-Client-IP");
       }
       if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){
           ip = request.getHeader("WL-Proxy-Client-IP");
       }
       if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){
           ip = request.getRemoteAddr();
       }
        return ip;
    }

九、通知地址 回調服務器 支付結果(這個回調 若是不返回給微信服務器 是否成功回調標示 則會一直回調8次 一直到返回成功標示位置) 代碼示例:

@Override
    public void payCallback(HttpServletRequest request,HttpServletResponse response) {
        logger.info("微信回調接口方法 start");
        logger.info("微信回調接口 操做邏輯 start");
        String inputLine = "";
        String notityXml = "";
        try {
            while((inputLine = request.getReader().readLine()) != null){
                notityXml += inputLine;
            }
            //關閉流
            request.getReader().close();
            logger.info("微信回調內容信息:"+notityXml);
            //解析成Map
            Map<String,String> map = doXMLParse(notityXml);
            //判斷 支付是否成功
            if("SUCCESS".equals(map.get("result_code"))){
                logger.info("微信回調返回是否支付成功:是");
                //得到 返回的商戶訂單號
                String outTradeNo = map.get("out_trade_no");
                logger.info("微信回調返回商戶訂單號:"+outTradeNo);
                //訪問DB
                WechatAppletGolfPayInfo payInfo = appletGolfPayInfoMapper.selectByPrimaryKey(outTradeNo);
                logger.info("微信回調 根據訂單號查詢訂單狀態:"+payInfo.getPayStatus());
                if("0".equals(payInfo.getPayStatus())){
                    //修改支付狀態
                    payInfo.setPayStatus("1");
                    //更新Bean
                    int sqlRow = appletGolfPayInfoMapper.updateByPrimaryKey(payInfo);
                    //判斷 是否更新成功
                    if(sqlRow == 1){
                        logger.info("微信回調  訂單號:"+outTradeNo +",修改狀態成功");
                        //封裝 返回值
                        StringBuffer buffer = new StringBuffer();
                        buffer.append("<xml>");
                        buffer.append("<return_code>SUCCESS</return_code>");
                        buffer.append("<return_msg>OK</return_msg>");
                        buffer.append("</xml>");
                        
                        //給微信服務器返回 成功標示 不然會一直詢問 我們服務器 是否回調成功
                        PrintWriter writer = response.getWriter();
                        //返回
                        writer.print(buffer.toString());
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

十、注意事項:

  1)、全部的簽名和發送微信服務器的數據必須一致 包括Key的大小寫 不然簽名失敗

  2)、微信小程序 前端調用 接口的時候 文檔上並無寫appId參數 該參數必定要穿 而且是大寫

  3)、交易類型 爲 JSAPI 的時候 則必須傳入openid

  4)、body格式問題 寫的是UTF-8 實際要的格式則是ISO8859-1 並且單獨對body進行設置好像很差使 因此必須所有都改爲該格式

  5)、生成簽名 最後加上key的那塊 加的格式是 &key = KEY 這種 並且不是直接 + key 這個地方須要注意一下 我碰了個坑 文檔沒看仔細

  6)、數據包ID 格式 不是 value直接設置成 數據包ID就能夠 前面須要加 "prepay_id="

  7)、最後一點強調 生成簽名的數據和發送服務器的數據 必須保持一致 

相關文章
相關標籤/搜索