1. 程式人生 > >微信公眾號支付的那些坑

微信公眾號支付的那些坑

在之前記錄了一下做微信公眾號支付的過程,但是有些混亂,之前做的內個也不是直接接的微信官方,而是轉接的別人在接的微信官方,他們賺個手續費,在這之後因為app停用了一段時間,上游公司把我們的appid給關掉了,所以打算從新接,直接接微信官方,好了這是背景。

我們做的是公眾號支付,也就是在微信網頁內部進行調取支付外掛進行支付的一個過程

1、登入後點擊產品中心, 點選公眾號支付


進入後就會看到這個頁面


因為我的已經開通所以就不需要了

點選開發配置

進行配置支付授權目錄:也就是你的支付頁面所在的目錄

一定是生產環境的,微信不支援 ip +埠 形式的地址   非同步通知也不支援,

所以測試都需要線上真實環境的域名+支付頁面所在目錄


登入公眾號平臺進行配置

公眾號的按鈕在下面

其次設定你的JS介面安全域名:也就是完整域名如:www.baidu.com


配置到這裡基本就算完成了

現在我們需要獲取幾個必須的引數

appid,mch_id ,加密key

基本配置按鈕也在下面

顯示appid  點選基本配置就會看到了 如:wxf8xxxxxxxxxfca


mch_id 就是你登入微信商戶後臺的內個號,如:1594xxxxxxxxx98 


key 獲取,也是在微信商戶後臺

這個是自己設定的,看你自己設定了,


1.        appid APPID (已有)

wxfxxxxxxxxxxxxxca

2.        mch_id 商戶ID (已有)

147xxxxxxxxxxx54

3.        nonce_str 隨機字串 , 生成UUID就可以了;

 UUID.randomUUID().toString().trim().replaceAll("-", "");

4.        sign 簽名 用WXPayUtil中的generateSignature(finalMap<String, String> data, String key)方法,data是將除了sign外,其他10個引數放到map中,key是四大配置引數中的API祕鑰(paternerKey)(這裡不要著急管它,最後處理它);

5.        body 所支付的名稱

6.        out_trade_no 自己後臺生成的訂單號,只要保證唯一就好:如“pay2018062521331”

7.        total_fee 支付金額 單位:分,為了測試此值給1,表示支付1分錢

8.        spbill_create_ip IP地址 網上很多ip的方法,自己找,此處測試給“127.0.0.1”

9.        notify_url 回撥地址:這是微信支付成功後,微信那邊會帶著一大堆引數(XML格式)請求這個地址多次,這個地址做我們業務處理如:修改訂單狀態,贈送積分等。Ps:支付還沒成功還想這麼遠幹嘛,最後再說。地址要公網可以訪問。

10.    trade_type 支付型別 咱們是公眾號支付此處給“JSAPI”

11.    openid 支付人的微信公眾號對應的唯一標識,每個人的openid在不同的公眾號是不一樣的,這11個引數裡,最費勁的就是他了,其他的幾乎都已經解決,現在開發得到這個引數。

    獲得openid的部分內容應該不屬於微信支付的範疇,屬於微信公眾號網頁授權的東西,詳情請參考微信網頁授權:

獲得openid步驟:

第一步:使用者同意授權,獲取code

https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect

注意:1. redirect_uri引數:授權後重定向的回撥連結地址請使用 urlEncode 對連結進行處理。

2. scope:用snsapi_base 

    通過此連結可以獲取code,可以在一個空頁面設定一個a標籤,連結至其redirect_uri的地址。點選a標籤,即可連結到redirect_uri的地址,並攜帶code。

  1. <ahref="https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx15c*********&redirect_uri=http%3a%2f%2fwww.***.com%2fpay.jsp&response_type=code&cope=snsapi_base#wechat_redirect">去支付頁面pay.jsp並攜帶code</a>

第二步:通過code換取網頁授權access_token(其實微信支付就沒有必要獲取access_token,咱們只要其中openid,不是要使用者資訊,此步結果已經就含有咱們需要的openid了)

獲取code,請求以下連結獲取access_token https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code

上一步的code有了,對於此連結的引數就容易了。可是在頁面上如何處理是個問題,我是在pay.jsp頁面載入完成後將獲取code當做引數傳非同步到後臺,在後臺中用http相關類傳送get請求(可以自行網上查詢)。返回的JSON結果為:

  1. { "access_token":"ACCESS_TOKEN",  
  2. "expires_in":7200,  
  3. "refresh_token":"REFRESH_TOKEN",  
  4. "openid":"OPENID",//就是它,只要這個值  
  5. "scope":"SCOPE" }  

好了都有了,我們就可以開始寫拼裝引數了, 引數填寫修改成你自己的就可以了

   HashMap<String, Object> reqMap = new HashMap<String, Object>()
            reqMap.put("appid", appid);
            reqMap.put("mch_id", mchid);
            reqMap.put("nonce_str", getUUid());
            reqMap.put("out_trade_no", "訂單號");
            reqMap.put("total_fee", 金額);
            reqMap.put("body", "所支付的名稱");
            reqMap.put("spbill_create_ip", getIp);
            reqMap.put("notify_url", notifyUrl);
            // reqMap.put("device_info", "WEB");
            reqMap.put("trade_type", "JSAPI");
            reqMap.put("openid", openId);

下面是工具類

/** 生成32位編碼
 * @return string
 */
    public static String getUUid() {
        String uuid = UUID.randomUUID().toString().trim().replaceAll("-", "");
        return uuid;
    }
/**
 * 獲取IP
 * @param request
 * @return
 */
    public String getRemortIP(HttpServletRequest request) {
        String remoteAddr = request.getRemoteAddr();
        String forwarded = request.getHeader("X-Forwarded-For");
        String realIp = request.getHeader("X-Real-IP");
        String ip = null;
        if (realIp == null) {
            if (forwarded == null) {
                ip = remoteAddr;
            } else {
                ip = remoteAddr + "/" + forwarded.split(",")[0];
            }
        } else {
            if (realIp.equals(forwarded)) {
                ip = realIp;
            } else {
                if (forwarded != null) {
                    forwarded = forwarded.split(",")[0];
                }
                ip = realIp + "/" + forwarded;
            }
        }
        return ip
    }

下面進行簽名,MD5工具類會貼出了的

 def md5str = createLinkString(reqMap);//排序
            System.out.println("---> md5str:" + md5str + "&key=" + wxapiKey);
            def sign = MD5Tool.md5(md5str + "&key=" + apiKey, charset).toUpperCase();簽名並轉大寫
            reqMap.put("sign", sign)
/**
 *  SignStr 待簽名字串
 * @param params
 * @return
 */
    public static String createLinkString(Map<String, Object> map) {
        System.out.println("帶簽名排序params:::::" + map.toString())
        List<String> keys = new ArrayList<String>(map.keySet());
        Collections.sort(keys);
        System.out.println("排序完Keys:::::" + keys.toString())
        def sb = new StringBuffer()
        for (int i = 0; i < keys.size(); i++) {
            String key = keys.get(i);
            Object value = map.get(key)
            System.out.println("key::" + key + "::value::" + value)
            if ("sign".equals(key) || "sign_type".equals(key) || value == null || value == "") {
                continue;
            }
            if (i == keys.size() - 1) {
                sb.append(key).append("=").append(value)
            } else {
                sb.append(key).append("=").append(value).append("&")
            }
        }
        def sbStr = sb.toString()
        System.out.println("===========簽名串:" + sbStr)
        return sbStr;
    }
package eims;

import org.apache.commons.codec.digest.DigestUtils;

import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Date;
//MD5工具類
public class MD5Tool {
    public static String md5(byte[] data) {
        MessageDigest md5 = null;
        try {
            md5 = MessageDigest.getInstance("MD5");
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            throw new IllegalStateException("System doesn't support MD5 algorithm.");
        }
        md5.update(data);

        byte[] encoded = md5.digest();
        StringBuffer buf = new StringBuffer();
        for (int i = 0; i < encoded.length; i++) {
            if ((encoded[i] & 0xff) < 0x10) {
                buf.append("0");
            }
            buf.append(Long.toString(encoded[i] & 0xff, 16));
        }

        return buf.toString();
    }
    /**
     * 簽名字元
     *
     * @param text
     *            要簽名的字元
     * @param key
     *            金鑰
     *            編碼格式
     * @return 簽名結果
     */
    public static String sign(String text, String key, String charset) throws Exception {
        text = text + key;
        return DigestUtils.md5Hex(getContentBytes(text, charset));
    }
    /**
     * @param content
     * @param charset
     * @return
     * @throws java.io.UnsupportedEncodingException
     */
    private static byte[] getContentBytes(String content, String charset) {
        if (charset == null || "".equals(charset)) {
            return content.getBytes();
        }
        try {
            return content.getBytes(charset);
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("簽名過程中出現錯,指定的編碼集不對,您目前指定的編碼集是:" + charset);
        }
    }
    public static String md5(String str, String charset) {
        if (str == null) return null;
        try {
            return md5( str.getBytes(charset) );
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            throw new IllegalStateException("System doesn't support Charset '" + charset + "'");
        }
    }
}

到這裡所有的引數都拼裝好了,微信要的是XML 格式的所以我們需要進行轉換

def requestXML = mapGetXML(reqMap);
System.out.println("req xml :\n" + requestXML);
String response = post(wxUrl, requestXML);

/**
 * map 轉 XML
 * @param map
 * @return
 */
    public static String mapGetXML(Map<String, String> map) {
        String xmlStr = null;
        StringBuffer sbf = new StringBuffer("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>");
        sbf.append("<xml>");
        for (Map.Entry<String, String> s : map.entrySet()) {
            sbf.append("<")
                    .append(s.getKey())
                    .append(">")
                    .append(s.getValue())
                    .append("</")
                    .append(s.getKey())
                    .append(">");
        }
        sbf.append("</xml>");
        xmlStr = sbf.toString();
        return xmlStr;
    }

XML也轉換好了,我們現在可以直接傳送請求了 格式xml

請求地址  :https://api.mch.weixin.qq.com/pay/unifiedorder

 String response = post(wxUrl, requestXML);

post方法

/**
 * 傳送xml資料請求到server端
 *
 * @param url
 *            xml請求資料地址
 * @param xmlString
 *            傳送的xml資料流
 * @return null傳送失敗,否則返回響應內容
 */
    public static String post(String url, String xmlFileName) {
        // 關閉
        System.setProperty("org.apache.commons.logging.Log", "org.apache.commons.logging.impl.SimpleLog");
        System.setProperty("org.apache.commons.logging.simplelog.showdatetime", "true");
        System.setProperty("org.apache.commons.logging.simplelog.log.org.apache.commons.httpclient", "stdout");

        // 建立httpclient工具物件
        HttpClient client = new HttpClient();
        // 建立post請求方法
        PostMethod myPost = new PostMethod(url);
        // 設定請求超時時間
        client.setConnectionTimeout(300 * 1000);
        String responseString = null;
        try {
            // 設定請求頭部型別
            myPost.setRequestHeader("Content-Type", "text/xml");
            myPost.setRequestHeader("charset", "utf-8");

            // 設定請求體,即xml文字內容,注:這裡寫了兩種方式,一種是直接獲取xml內容字串,一種是讀取xml檔案以流的形式
            // myPost.setRequestBody(xmlString);

            // InputStream
            // body=this.getClass().getResourceAsStream("/"+xmlFileName);
            // myPost.setRequestBody(body);
            myPost.setRequestEntity(new StringRequestEntity(xmlFileName, "text/xml", "utf-8"));
            int statusCode = client.executeMethod(myPost);
            if (statusCode == HttpStatus.SC_OK) {
                BufferedInputStream bis = new BufferedInputStream(myPost.getResponseBodyAsStream());
                byte[] bytes = new byte[1024];
                ByteArrayOutputStream bos = new ByteArrayOutputStream();
                int count = 0;
                while ((count = bis.read(bytes)) != -1) {
                    bos.write(bytes, 0, count);
                }
                byte[] strByte = bos.toByteArray();
                responseString = new String(strByte, 0, strByte.length, "utf-8");
                bos.close();
                bis.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        myPost.releaseConnection();
        return responseString;
    }

搞了這麼多終於看到點結果了


請求過後 微信端返回的也是XML 不利於我們處理,所以繼續轉map
  Map<String, Object> respMap = xmlString2Map(response);

xml 轉map

/**
 * Xml string轉換成Map
 * @param xmlStr
 * @return
 */
    public static Map<String, Object> xmlString2Map(String xmlStr) {
        Map<String, Object> map = new HashMap<String, Object>();
        Document doc;
        try {
            doc = DocumentHelper.parseText(xmlStr);
            Element el = doc.getRootElement();
            map = recGetXmlElementValue(el, map);
        } catch (DocumentException e) {
            e.printStackTrace();
        }
        return map;
    }

轉好map 後我們就開始取 prepay_id

搞了這麼久就是為了丫的, 取出來後我們還需要把引數拼裝一遍,扔到頁面,掉起 JS 外掛進行支付

引數:

appid也有

timeStamp時間戳 你們new Date();即可,因為我語言是 Groovy 所以需要getTime 才是秒數

package   prepay_id   已有了

nonceStr    隨機數  getuuid方法就可以了

signType     固定值  MD5

sign           上面5個引數的簽名結果

這裡值得注意的是package 引數, 這個引數可不是簡單的吧prepay_id 放進去

,要把 “prepay_id=”這個拼接上裡面不能有多餘的"或者'符號

之前沒有拼接 ,微信支付的時候返回 缺少total_fee引數, 可是上一步給微信傳的時候並沒有少,微信返回的都成功了
所以還是拋頁面的時候出現的錯誤,害我整了好久。

 String retCode = respMap.get("result_code");
            if ("SUCCESS".equals(retCode)) {
               String prepay_id = respMap.get("prepay_id");
                Map map = new HashMap();
                map.put("appId", wxappid);
                map.put("timeStamp", new Date().getTime());
                map.put("package", "prepay_id=" + prepay_id);
                map.put("nonceStr", getUUid());
                map.put("signType", "MD5");
                def newmd5str = createLinkString(map);
                System.out.println("ShoppingCartService---> md5str:" + newmd5str + "&key=" + wxapiKey);
                def newSign = MD5Tool.md5(newmd5str + "&key=" + wxapiKey, charset).toUpperCase();
                map.put("paySign", newSign);
            }

引數裝完後直接傳到頁面,看自己框架了,我就不貼了

下面直接把頁面貼出了 值得注意的是

package 這個引數 ,在頁面是一個域 ,所以在後臺傳的時候重新改個名 我改的是paypackage 

<%@ page contentType="text/html;charset=UTF-8" %>
<html>
<head>
    <title>微信支付</title>
    <meta http-equiv="content-type" content="text/html;charset=utf-8"/>
</head>
<script>
    function onBridgeReady() {
        <%
            System.out.println("incoming...");
            System.out.println(request.getParameter("appId"));
            System.out.println(request.getParameter("timeStamp"));
            System.out.println(request.getParameter("nonceStr"));
            System.out.println(request.getParameter("paypackage"));
            System.out.println(request.getParameter("signType"));
            System.out.println(request.getParameter("paySign"));
            System.out.println('deviceType:::'+request.getParameter("deviceType"));
            System.out.println("incoming...");
        %>
        WeixinJSBridge.invoke(
            'getBrandWCPayRequest', {
                "appId": "<%= request.getParameter("appId") %>",     //公眾號名稱,由商戶傳入
                "timeStamp": "<%= request.getParameter("timeStamp") %>",         //時間戳,自1970年以來的秒數
                "nonceStr": "<%= request.getParameter("nonceStr") %>", //隨機串
                "package": "<%= request.getParameter("paypackage") %>",
                "signType": "<%= request.getParameter("signType") %>",         //微信簽名方式:
                "paySign": "<%= request.getParameter("paySign") %>" //微信簽名,paySign 採用統一的微信支付 Sign 簽名生成方法,注意這裡 appId 也要參與簽名,appId 與 config 中傳入的 appId 一致,即最後參與簽名的引數有appId, timeStamp, nonceStr, package, signType。
            },
            function (res) {
                if (res.err_msg == "get_brand_wcpay_request:ok") {     // 使用以上方式判斷前端返回,微信團隊鄭重提示:res.err_msg將在使用者支付成功後返回    ok,但並不保證它絕對可靠。
                    alert('支付成功!');
                   
                } else if (res.err_msg == "get_brand_wcpay_request:cancel") {
                    alert('已取消微信支付!');
                } else {
                    alert('支付失敗!' + res.err_msg)
                }
                /*WeixinJSBridge.call('closeWindow');*/  //關閉微信端視窗
            }
        );
    }

    if (typeof WeixinJSBridge == "undefined") {
        if (document.addEventListener) {
            document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false);
        } else if (document.attachEvent) {
            document.attachEvent('WeixinJSBridgeReady', onBridgeReady);
            document.attachEvent('onWeixinJSBridgeReady', onBridgeReady);
        }
    } else {
        onBridgeReady();
    }
</script>
</html>


到這裡就完成了,微信支付只能去生產上面測試,這是比較操蛋的
如果按照以上的步驟一步步走的話不會出現問題的,如有問題請留言,看到會及時答覆(有人看嗎,哈哈哈哈哈夠嗆!!

相關推薦

公眾支付

系統 shm efi bsp 網絡異常 router nec 平臺 wiki   前兩周做微信H5支付,在瀏覽器端用的,天真地以為app掛到公眾號中也能用,結果不行>"<|||| ,只好再對接一次公眾號支付,微信的支付對接下來總體感覺就是封裝地不如支付寶,文檔不

公眾支付:呼叫支付jsapi缺少引數 timeStamp等錯誤解決方法

  這段時間一直比較忙,一忙起來真感覺自己就只是一臺掙錢的機器了(說的好像能掙到多少錢似的,呵呵);這會兒難得有點兒空閒時間,想把前段時間開發微信公眾號支付遇到問題及解決方法跟大家分享下,這些“暗坑”能不掉就不掉吧,要不然關鍵時刻出問題,真是讓人急的焦頭爛額。      雙12客戶的商城活動正在蓄勢進行

公眾支付那些

在之前記錄了一下做微信公眾號支付的過程,但是有些混亂,之前做的內個也不是直接接的微信官方,而是轉接的別人在接的微信官方,他們賺個手續費,在這之後因為app停用了一段時間,上游公司把我們的appid給關掉了,所以打算從新接,直接接微信官方,好了這是背景。我們做的是公眾號支付,也

vue項目使用公眾支付總結及遇到的

ati 圖片 for css 支付接口 this static adding cti 微信公眾號支付 1. 使用jssdk調用微信支付,具體查看開發文檔; 使用的vuex,在mutations中 ? 1 2 3 4 5 6 7 8 9 10 11 12 13

get_brand_wcpay_request:fail,公眾支付的那點

微信公眾號支付一直提示“支付驗證簽名失敗”,明明簽名沒有問題,用微信驗證簽名工具(點選開啟連結)驗證簽名也沒問題,但就是在支付的時候提示“支付驗證簽名失敗”, 我忍不住爆粗口,問題出在哪呢?微信支付需要先獲取預支付id(也就是prepay_id),然後通過prepay_id

公眾支付到底有多

先吐個嘈,接個微信支付,斷斷續續一個多星期。。。。。。要是趕著上線的話,黃花菜都涼了,哪裡錯了根本不給提示。。。好了,我們來梳理一下介入流程,其實極其簡單!首先要區分的是你接的是不是公眾號支付:公眾號支付指的是在微信公眾號或者微信瀏覽器內調起H5支付,不要跟其他瀏覽器的H5支

公眾支付報文示例

cda 響應 xca mes amp 鏈接 返回值 col http 請求報文: 1 <xml> 2 <body><![CDATA[狄克酸奶店]]></body> 3 <callback_url&

個體戶沒有組織機構代碼證如何開通公眾支付

jpg alt 註意 方法 .com str 工作人員 個體工商戶 log 個體工商戶開通微信支付最新流程: 一、準備資料1.營業執照:有效期內的個體戶執照;2.身份證:經營者個人身份證照片;3.收款銀行賬戶:提現用的銀行賬號;4.手機號碼:客服人員的聯系手機號;5.郵箱

php公眾支付接口開發demo

targe param pre space secret 修改 pen host field 本支付接口使用Yii2框架,所以控制器的格式都是該框架的,不過放到其他框架都差不多,根據對應的規則修改一下控制器的方法名字就行了,親測有效,比較簡單,沒有封裝,想了解微信支付實現

公眾支付--錯誤記錄

二次 數組 格式轉換 println equals sig col package reat 微信公眾號支付調用統一下單接口時,微信返回的數據一定要二次組裝再給前臺,否則會有問題的,正確示範如下: /** * 獲取weixin支付的返回信息 * @pa

支付公眾支付) [記錄]

scope err question dir rec package ready fad span 後臺   先獲取code code有效5min     public string GetCodeUrl(string Appid, string redirect

thinkphp整合系列之公眾支付

const simple 商品 simplex 支付平臺 doc 外部 center vendor thinkphp整合系列之微信公眾號支付 白俊遙 2016-07-17 11:26:52 PHP thinkphp 公眾號支付是指在微信app中訪問的頁面

***公眾支付+H5支付+掃碼支付+小程序支付+APP支付解決方案總結

ati asc alt creat chapter edit 隨機字符串 glob 測試 最近負責的一些項目開發,都用到了微信支付(微信公眾號支付、微信H5支付、微信掃碼支付、APP微信支付)。在開發的過程中,在調試支付的過程中,或多或少都遇到了一些問題,今天總結下,分享,

公眾支付開發全過程(java版)

sdk 命令 所有 data 權限 {} servle res ast 文章有不當之處,歡迎指正,如果喜歡微信閱讀,你也可以關註我的微信公眾號:好好學java,獲取優質學習資源。 一、微信官方文檔微信支付開發流程(公眾號支付) 首先我們到微信支付的官方文檔的開發步驟部分查

公眾支付

存在 分享圖片 itl 操作 mark clas 思考 域名認證 span 近期處理微信公眾號支付過程中遇到一些小問題,也因此引發了一些思考。 首先不得不吐槽一下微信公眾號的配置文檔沒有及時更新,對開發人員不夠細致,也因此迷茫了好久。 經過一輪研究和實

vue項目使用公眾支付總結

tor 即將 script mut 頁面 com vue log string 微信公眾號支付 1. 使用jssdk調用微信支付,具體查看開發文檔; 使用的vuex,在mutations中 wechatPay (state, data) { sta

公眾支付(tp5)

                                        微信公眾號支付

thinkphp3.2公眾支付(jsapi支付)開發過程

第一次做微信支付(網頁版本的),折騰了兩天,記錄一下方便下次自己再次使用,也希望能幫和我一樣初次接觸的朋友踩一下坑。 前期準備 1.開通微信認證服務號,並且開通商戶平臺 2.下載微信支付dome,下載地址:https://pay.weixin.qq.com/wiki/doc/api/js

公眾支付/退款(java環境)開發介紹

開發之前翻閱了很多帖子,結合自己的實際開發情況,將微信支付/退款 流程以及code貼出,希望通過這一篇帖子就能解決你的問題,有不清楚的直接留言,我會及時回覆(ง •̀_•́)ง   一些說明:xxxUtils為工具類,Constant為常量類 為方便開發,所用和微信支付相關co

公眾支付JSAPI 詳細記錄

剛剛除錯通微信公眾號支付,寫個部落格記錄一下。 jsapi必要的幾個引數 微信公眾號的賬戶密碼,微信商戶賬號密碼. 登陸微信公眾號,左下角開發-基本配置,檢視APPID 1、公眾APPID(已經得到) 2、APPSECEPT(已經得到)   進入微信商戶平