Anroid微信支付從統一下單到喚起支付
阿新 • • 發佈:2019-02-20
專案需要整合微信支付功能,老是返回-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解析後的鍵名是帶下劃線的,鍵名是全小寫字母。