微信小程序支付+java後臺實現(完整版)

前言:本人是從android 轉java的,第一次加入項目就遇到了微信支付對接,微信支付的對接文檔讓我感覺很差,也遇到了很多的坑,所以在此記錄一下。

 

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------

一:先了解微信小程序支付的流程,官方給的流程圖還是可以看的

å°ç¨åºæ¯ä»æ¶åºå¾

我們後臺要做的就只有三點:

1.通過小程序的appid 祕鑰  code 三個參數拿到小程序的openid(每個登錄小程序的用戶openid是唯一的)

2.然後通過openid,還有訂單參數,小程序appid,商戶mch_id等(開發文檔有明確必傳參數,下面會給代碼)拿到支付id prepay_id返回給前端

(此處需要注意的就是兩次簽名的生成)

3.對微信後臺返回的支付結果進行數據處理

二:主要代碼部分

1.如何拿到小程序的openid

puclic class WeiXinUtil{

    public static String getOpenid(String code){  
        Map<String, String> params = new HashMap<>();
        params.put("appid", "微信小程序開發平臺註冊信息");
        params.put("secret", "微信小程序開發平臺的祕鑰");
        params.put("grant_type", "authorization_code");
        params.put("js_code", "微信小程序登錄後傳來的code參數");
        Map map =  JSON.parseObject(HttpClientUtil.postMap("https://api.weixin.qq.com/sns/jscode2session", params),Map.class);
        System.out.println(map);
        assert map != null;
        if (map.get("openid") == null) {
            throw new BusinessException(map.get("errmsg").toString());
        }
        return map.get("openid").toString();
    }
}

代碼段解釋:params中的參數都是小程序那邊給出的,grant_type:參數固定爲autorization_code,然後將參數使用post請求發到微信的接口中https://api.weixin.qq.com/sns/jscode2session,從返回值中拿到openid

2.如何將商品信息簽名,去微信後臺驗證拿到支付id prepay_id(這一步的操作就叫統一下單)

統一下單的參數給了很多,我們只傳他必填參數

接口代碼

 @ApiOperation("微信支付,預下單")
    @PostMapping("/xxxxx")
    public RestResult wxpay(@RequestBody PayRequestBodyVo vo) {
        try {
            String unifiedorder = WeiXinUtil.getUnifiedorder(openid, outTradeNo, Double.valueOf(vo.getSumValue()));
            Map<String, String> map = PayUtil.doXMLParse(unifiedorder);
            SortedMap<Object, Object> param = WeiXinUtil.prepayId(map);
            return new RestResult("微信支付", param);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return new RestResult("微信支付");
    }
public class WeiXinUtil{
    public static String getUnifiedorder(String openid, String outTradeNo, double money) throws Exception {
        SortedMap<Object, Object> params = new TreeMap<>();
        params.put("appid", "小程序appid");
        params.put("body", "商品名稱");
        params.put("mch_id", "商戶mch_id");
        params.put("nonce_str", PayUtil.makeUUID(32));
        params.put("notify_url", "支付結果返回接口地址,需要自己寫個接口,然後做外網映射(外網映射建議做http的,https有的人因爲缺少ssl證書會導致接受不到返回值)");
        params.put("openid", "上一步獲取到的openid");
        params.put("out_trade_no", PayUtil.generateOrderNo());//商品訂單號
        params.put("spbill_create_ip", PayUtil.getLocalIp());//服務部署的ip
        params.put("total_fee", PayUtil.moneyToIntegerStr(money));//費用的參數轉型
        params.put("trade_type", "JSAPI");//對接類型
        params.put("sign", PayUtil.createSign("UTF-8", params, "商戶祕鑰"));//MD5簽名
        //轉換成xml
        String xmlData = PayUtil.getRequestXml(params);
        //請求微信後臺,獲取支付id
        String resXml = HttpClientUtil.httpsRequest("https://api.mch.weixin.qq.com/pay/unifiedorder", "POST", xmlData);

        return resXml;
    }
    public static SortedMap<Object, Object> prepayId(Map<String, String> map) {
        SortedMap<Object, Object> parameters = new TreeMap<>();
        parameters.put("appId", "");
        parameters.put("timeStamp", PayUtil.create_timestamp());
        parameters.put("nonceStr", map.get("nonce_str"));
        parameters.put("package", "prepay_id=" + map.get("prepay_id"));
        parameters.put("signType", "MD5");
        String sign = PayUtil.createSign("UTF-8", parameters, "商戶祕鑰");
        parameters.put("prepay_id", "prepay_id=" + map.get("prepay_id"));
        parameters.put("paySign", sign);
        return parameters;
    }
}

然後給出這裏使用到的工具方法 

public class PayUtil{
    /**
     * 獲取當前機器的ip
     */
    public static String getLocalIp() {
        InetAddress ia = null;
        String localip = null;
        try {
            ia = ia.getLocalHost();
            localip = ia.getHostAddress();
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }
        return localip;
    }
@SuppressWarnings("rawtypes")
    public static String getRequestXml(SortedMap<Object, Object> parameters) {
        StringBuffer sb = new StringBuffer();
        sb.append("<xml>");
        Set es = parameters.entrySet();
        Iterator it = es.iterator();
        while (it.hasNext()) {
            Map.Entry entry = (Map.Entry) it.next();
            String k = (String) entry.getKey();
            String v = (String) entry.getValue();
            if ("attach".equalsIgnoreCase(k) || "body".equalsIgnoreCase(k) || "sign".equalsIgnoreCase(k)) {
                sb.append("<" + k + ">" + "<![CDATA[" + v + "]]></" + k + ">");
            } else {
                sb.append("<" + k + ">" + v + "</" + k + ">");
            }
        }
        sb.append("</xml>");
        return sb.toString();
    }
    /**
     * 創建簽名Sign
     */
    @SuppressWarnings("rawtypes")
    public static String createSign(String characterEncoding,SortedMap<Object, Object> parameters, String key) {
        StringBuffer sb = new StringBuffer();
        Set es = parameters.entrySet();
        Iterator<?> it = es.iterator();
        while (it.hasNext()) {
            Map.Entry entry = (Map.Entry) it.next();
            String k = (String) entry.getKey();
            if (entry.getValue() != null || !"".equals(entry.getValue())) {
                String v = String.valueOf(entry.getValue());
                if (null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)) {
                    sb.append(k + "=" + v + "&");
                }
            }
        }
        sb.append("key=" + key);
        String sign = MD5Util.MD5Encode(sb.toString()).toUpperCase();
        return sign;
    }
     /**
     * 生成隨機數
     */
    public static String makeUUID(int len) {
        return UUID.randomUUID().toString().replaceAll("-", "").substring(0, len);
    }
    /**
     * 生成訂單號
     */
    public static String generateOrderNo() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyMMdd");
        return sdf.format(new Date()) + makeUUID(16);
    }
    /**
     * 解析xml
     */
    public static Map doXMLParse(String strxml) throws Exception {
        strxml = strxml.replaceFirst("encoding=\".*\"", "encoding=\"UTF-8\"");
        if (null == strxml || "".equals(strxml)) {
            return null;
        }
        Map m = new HashMap();
        InputStream in = new ByteArrayInputStream(strxml.getBytes("UTF-8"));
        SAXBuilder builder = new SAXBuilder();
        org.jdom2.Document doc = builder.build(in);
        org.jdom2.Element root = doc.getRootElement();
        List list = root.getChildren();
        Iterator it = list.iterator();
        while (it.hasNext()) {
            org.jdom2.Element e = (org.jdom2.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;
    }
    /**
     * 獲取子節點的xml
     */
    public static String getChildrenText(List children) {
        StringBuffer sb = new StringBuffer();
        if (!children.isEmpty()) {
            Iterator it = children.iterator();
            while (it.hasNext()) {
                org.jdom2.Element e = (org.jdom2.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();
    }
    /**
     * 轉換金額到整型
     */
    public static String moneyToIntegerStr(Double money) {
        BigDecimal decimal = new BigDecimal(money);
        int amount = decimal.multiply(new BigDecimal((100)))
                .setScale(0, BigDecimal.ROUND_HALF_UP).intValue();
        return String.valueOf(amount);
    }
   /**
     * 微信下單,map to xml
     * @param params 參數
     * @return String
     */
    public static String mapToXml(Map<String, String> params) {
        StringBuilder xml = new StringBuilder();
        xml.append("<xml>");
        for (Map.Entry<String, String> entry : params.entrySet()) {
            String key   = entry.getKey();
            String value = entry.getValue();
            // 略過空值
            if (StringUtils.isEmpty(value)) continue;
            xml.append("<").append(key).append("><![CDATA[");
            xml.append(entry.getValue());
            xml.append("]]></").append(key).append(">");
        }
        xml.append("</xml>");
        return xml.toString();
    }

    public static String create_timestamp() {
        return Long.toString(System.currentTimeMillis() / 1000);
    }

}

本人在此踩過的坑,1.由於是兩個人合作,商戶mch_id寫錯了,請求下單返回結果 商戶有違規,限制支付功能,改正商戶id即解決

2.簽名錯誤:此處代碼已經嘗試過了 簽名沒有錯誤,但是本人在做這個地方的時候也遇到了些坑,請求結果爲簽名錯誤,然後去微信簽名驗證工具中驗證:

將轉化後的xml簽名信息放入,然後將商戶平臺設置的祕鑰放入商戶key中,若驗證通過,兩個key相同 即你的簽名沒有問題,

若此處代碼中還返回簽名錯誤,請重新到商戶賬號中,重新設置祕鑰,(這個時候一定是你設置的祕鑰,和你代碼總驗證的祕鑰不一致,這裏的錯誤就多種原因了:可能設置後複製粘貼的錯誤,可能是商戶那邊操作人員給你的時候就是錯的,所以在這裏請重新設置一次)

三:微信返回結果接口

返回結果的接口就不上代碼了,這裏都是項目的個人操作,唯一需要注意的就是能否拿到微信後臺返回的數據

1.返回數據是null的:本人遇到的原因是(使用花生殼做了這個接口的外網映射,由於微信開發平臺要求說要https的,但測試的時候就拿不到返回值),解決方式:將映射類型變成http就可以拿到返回值了

-------------------------------------------------------------------------

---------------若有地方表達不明白,歡迎加QQ:574774295 詢問,請備註驗證消息

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章