1. 程式人生 > >Anroid微信支付從統一下單到喚起支付

Anroid微信支付從統一下單到喚起支付

專案需要整合微信支付功能,老是返回-1,反反覆覆看文件,還有一條條看官方demo程式碼,看了三天看到吐血。

我覺得可以把微信寫官方文件的人拉出去殺了祭天,官方文件都那麼坑。

所以分享一下,給各位免得踩一樣的坑。

下面是下單和喚起的方法,一定要看仔細了。

//微信支付
private void wxPay() {
  String appid = PublicStaticUtil.WX_APPID;//app在微信申請的appid
        String mch_id = "1400000000";//申請支付功能的商戶號
        String nonce_str = getRandomString();//隨機碼
        String body = "test";//商品描述,隨便寫
        String out_trade_no = orderInfo.getOrderNo();//訂單編號
        int total_fee = orderInfo.getPrice();//總金額
        String time_start = getCurrentTime();//時間戳,格式yyyyMMddHHmmss
        String trade_type = "APP";//交易型別
        String notify_url = "http://xx.xx.xx/";//通知回撥地址,然後後臺做個回撥,無參的,必須放到商戶平臺上配置新增
        String spbill_create_ip = getIPAddress(PayOrderActivity.this);//裝置ip
        SortedMap<String, String> params = new TreeMap<String, String>();
        params.put("appid",appid);
        params.put("mch_id",mch_id);
        params.put("nonce_str",nonce_str);
        params.put("body",body);
        params.put("out_trade_no",out_trade_no);
        params.put("total_fee",total_fee+"");
        params.put("time_start",time_start);
        params.put("trade_type",trade_type);
        params.put("notify_url",notify_url);
        params.put("spbill_create_ip",spbill_create_ip);

        //獲取簽名sign,文件有些引數是必須傳的,但是少了還是能請求成功xml
        String sign = getSign(params);
        //引數xml化
        String xmlParams = parseString2Xml(params,sign);
        LogUtil.e("------下單xml化---->"+"\n"+xmlParams);
        String url = "https://api.mch.weixin.qq.com/pay/unifiedorder";//下單統一介面
        //下單請求
        Map<String,String> map = new HashMap<>();
        map.put("XML",xmlParams);
        OkHttpManager.getInstance().postAsync(url, map, new OkHttpManager.DataCallBack() {//我用okhttp3請求的post,這個不重要,其他的也行
            @Override
            public void requestFailure(Request request, Exception e) {
                Toast.makeText(PayOrderActivity.this, "網路連線失敗!", Toast.LENGTH_SHORT).show();
            }

            @Override
            public void requestSuccess(Object result) throws Exception {
                String xmlStr = ((String) result);
                LogUtil.e("----下單返回--->"+"\n"+xmlStr);
                Map<String, String> map = decodeXml(xmlStr);
                Set set = map.entrySet();
                Iterator iterator = set.iterator();
                while (iterator.hasNext()){
                    Map.Entry entry = (Map.Entry) iterator.next();
                    LogUtil.e("-----entry.getKey()/entry.getValue()---->"+entry.getKey()+"/"+entry.getValue());
                }
                if (map.get("return_code").equalsIgnoreCase("SUCCESS")) {
                    if (map.get("return_msg").equalsIgnoreCase("OK")) {
                        PayReq req = new PayReq();
                        req.appId = PublicStaticUtil.WX_APPID;//APPID
                        req.partnerId = "1400000000";//商戶號
                        req.prepayId = map.get("prepay_id");
                        req.nonceStr = map.get("nonce_str");
                        String time = System.currentTimeMillis() / 1000 + "";
                        req.timeStamp = time;//時間戳,這次是擷取long型別時間的前10位
                        req.packageValue = "Sign=WXPay";//引數是固定的,寫死的
                        SortedMap<String, String> params = new TreeMap<String, String>();//一定要注意鍵名,去掉下劃線,字母全是小寫
                        params.put("appid", PublicStaticUtil.WX_APPID);
                        params.put("partnerid","1493732082");
                        params.put("prepayid",map.get("prepay_id"));
                        params.put("noncestr",map.get("nonce_str"));
                        params.put("timestamp",time);
                        params.put("package","Sign=WXPay");
                        req.sign = getSign(params);//重新存除了sign欄位之外,再次簽名,要不然喚起微信支付會返回-1,特別坑爹的是,鍵名一定要去掉下劃線,不然返回-1
                        LogUtil.e("====================================================");
                        LogUtil.e("-----appid---"+req.appId);
                        LogUtil.e("-----partnerId---"+req.partnerId);
                        LogUtil.e("-----prepayId---"+req.prepayId);
                        LogUtil.e("-----nonceStr---"+req.nonceStr);
                        LogUtil.e("-----timeStamp---"+req.timeStamp);
                        LogUtil.e("-----packageValue---"+req.packageValue);
                        LogUtil.e("-----sign---"+req.sign);
                        LogUtil.e("====================================================");
                        YDZApplication.getWXAPI().sendReq(req);//因為我已經在application註冊了微信,全域性呼叫
                    } else {
                        Toast.makeText(PayOrderActivity.this, "簽名失敗", Toast.LENGTH_SHORT).show();
                    }

                } else {
                    Toast.makeText(PayOrderActivity.this, "交易失敗", Toast.LENGTH_SHORT).show();
                }
            }
        });
}

下面是呼叫到方法
    /**
     * 解析返回的XML為鍵值對
     * @param content
     * @return
     */
    public Map<String,String> decodeXml(String content) {

        try {
            Map<String, String> xml = new HashMap<String, String>();
            XmlPullParser parser = Xml.newPullParser();
            parser.setInput(new StringReader(content));
            int event = parser.getEventType();
            while (event != XmlPullParser.END_DOCUMENT) {

                String nodeName=parser.getName();
                switch (event) {
                    case XmlPullParser.START_DOCUMENT:

                        break;
                    case XmlPullParser.START_TAG:

                        if("xml".equals(nodeName)==false){
                            //例項化student物件
                            xml.put(nodeName,parser.nextText());
                        }
                        break;
                    case XmlPullParser.END_TAG:
                        break;
                }
                event = parser.next();
            }

            return xml;
        } catch (Exception e) {
            LogUtil.e(e.toString());
        }
        return null;

    }


    /***
     * 將鍵值對xml化
     * @param map
     * @param sign
     * @return
     */
    private String parseString2Xml(SortedMap<String, String> map, String sign) {
        StringBuffer sb = new StringBuffer();
        map.put("sign",sign);
        sb.append("<xml>");
        Set es = map.entrySet();
        Iterator iterator = es.iterator();
        while(iterator.hasNext()){
            Map.Entry entry = (Map.Entry)iterator.next();
            String k = (String)entry.getKey();
            String v = (String)entry.getValue();
            sb.append("<"+k+">"+v+"</"+k+">");
        }
        sb.append("</xml>");
        return sb.toString();
    }

    /**
     * 簽名獲得sign欄位
     * @param params
     * @return
     */
    private String getSign(SortedMap<String, String> params) {
        StringBuffer sb = new StringBuffer();
        Set es = params.entrySet();
        Iterator iterator = es.iterator();
        while (iterator.hasNext()){
            Map.Entry entry = (Map.Entry) iterator.next();
            String key = (String) entry.getKey();
            String value = (String) entry.getValue();
            if (null != value && !TextUtils.isEmpty(value) && !key.equals("key")){
                sb.append(key + "=" + value + "&");
            }
        }
        sb.append("key="+"aaAAbbBBccCCddDDeeEEffFFggGGhhHH");//商品平臺API金鑰,32位的字母數字,找申請支付功能的人要,就在商戶平臺那裡
        LogUtil.e("-------------------------------sign-------------->"+"\n"+sb.toString());
        String packageSign = MD5.getMessageDigest(sb.toString().getBytes()).toUpperCase();
        return packageSign;
    }

    /**
     * MD5加密
     * @return
     */
    private String getRandomString() {
        Random random = new Random();
        return MD5.getMessageDigest(String.valueOf(random.nextInt(10000)).getBytes());
    }


    /**獲取裝置ip*/
    public String getIPAddress(Context context) {
        NetworkInfo info = ((ConnectivityManager) context
                .getSystemService(Context.CONNECTIVITY_SERVICE)).getActiveNetworkInfo();
        if (info != null && info.isConnected()) {
            if (info.getType() == ConnectivityManager.TYPE_MOBILE) {//當前使用2G/3G/4G網路
                try {
                    //Enumeration<NetworkInterface> en=NetworkInterface.getNetworkInterfaces();
                    for (Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements(); ) {
                        NetworkInterface intf = en.nextElement();
                        for (Enumeration<InetAddress> enumIpAddr = intf.getInetAddresses(); enumIpAddr.hasMoreElements(); ) {
                            InetAddress inetAddress = enumIpAddr.nextElement();
                            if (!inetAddress.isLoopbackAddress() && inetAddress instanceof Inet4Address) {
                                return inetAddress.getHostAddress();
                            }
                        }
                    }
                } catch (SocketException e) {
                    e.printStackTrace();
                }

            } else if (info.getType() == ConnectivityManager.TYPE_WIFI) {//當前使用無線網路
                WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
                WifiInfo wifiInfo = wifiManager.getConnectionInfo();
                String ipAddress = intIP2StringIP(wifiInfo.getIpAddress());//得到IPV4地址
                return ipAddress;
            }
        } else {
            //當前無網路連線,請在設定中開啟網路
        }
        return null;
    }

    /**
     * 將得到的int型別的IP轉換為String型別
     *
     * @param ip
     * @return
     */
    public String intIP2StringIP(int ip) {
        return (ip & 0xFF) + "." +
                ((ip >> 8) & 0xFF) + "." +
                ((ip >> 16) & 0xFF) + "." +
                (ip >> 24 & 0xFF);
    }

MD5加密類
import java.security.MessageDigest;

public class MD5 {

	private MD5() {}
	
	public final static String getMessageDigest(byte[] buffer) {
		char hexDigits[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
		try {
			MessageDigest mdTemp = MessageDigest.getInstance("MD5");
			mdTemp.update(buffer);
			byte[] md = mdTemp.digest();
			int j = md.length;
			char str[] = new char[j * 2];
			int k = 0;
			for (int i = 0; i < j; i++) {
				byte byte0 = md[i];
				str[k++] = hexDigits[byte0 >>> 4 & 0xf];
				str[k++] = hexDigits[byte0 & 0xf];
			}
			return new String(str);
		} catch (Exception e) {
			return null;
		}
	}
}

還有
import java.security.MessageDigest;


public class MD5Util {

    private static String byteArrayToHexString(byte b[]) {
        StringBuffer resultSb = new StringBuffer();
        for (int i = 0; i < b.length; i++)
            resultSb.append(byteToHexString(b[i]));

        return resultSb.toString();
    }

    private static String byteToHexString(byte b) {
        int n = b;
        if (n < 0)
            n += 256;
        int d1 = n / 16;
        int d2 = n % 16;
        return hexDigits[d1] + hexDigits[d2];
    }

    public static String MD5Encode(String origin, String charsetname) {
        String resultString = null;
        try {
            resultString = new String(origin);
            MessageDigest md = MessageDigest.getInstance("MD5");
            if (charsetname == null || "".equals(charsetname))
                resultString = byteArrayToHexString(md.digest(resultString
                        .getBytes()));
            else
                resultString = byteArrayToHexString(md.digest(resultString
                        .getBytes(charsetname)));
        } catch (Exception exception) {
        }
        return resultString;
    }

    private static final String hexDigits[] = {"0", "1", "2", "3", "4", "5",
            "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"};
}

真的是看文件看到眼瞎吐血都找不到錯在哪,最後是曾經掉進過坑的同事發現錯的地方。

建議統一下單介面,解析微信返回的xml,還有再次簽名sign都要放到後臺完成,這一段,後臺也能參照我這篇對比一下錯在哪,

最好app終端只負責喚起微信支付就好了,只做下面的事情

PayReq req = new PayReq();
req.appId = xx;//公司後臺傳
req.partnerId = xx;//公司後臺傳
req.prepayId = xx;//公司後臺傳
req.nonceStr = xx;//公司後臺傳
req.timeStamp = xx;//公司後臺傳
req.packageValue = xx;//公司後臺傳
req.sign = xx;//公司後臺傳
YDApplication.getWXAPI().sendReq(req);

如果喚起微信返回baseResp.errCode == -1,

檢檢視看在獲取到微信返回的xml後,是否再次加密sign了?

加密的時候鍵名是否去掉了下劃線,因為返回的XML解析後的鍵名是帶下劃線的,鍵名是全小寫字母。