最近項(xiàng)目中需要支付功能,一個(gè)是支付寶,一個(gè)是微信。由于之前都沒有接觸過,所以在網(wǎng)上看了許多文章與文檔,逐步了解了這兩個(gè)支付,項(xiàng)目中配置完成后,也順便記錄一下配置的環(huán)節(jié),以便以后查看使用。
說實(shí)在的,支付寶的開發(fā)文檔寫的比微信的支付文檔 好太多!!(不吹不黑)。
一、參考的文章
1.Android微信支付爬坑 (http://blog.csdn.net/ywl5320/article/details/50856922#reply)
這篇文章是由App客戶端完全同微信服務(wù)器進(jìn)行交互,自家的服務(wù)器不參與。官方不提倡,但是為了更好地明白原理,還是很好的
2.Android App支付系列(http://blog.csdn.net/xiong_it/article/details/51685033)
這篇文章是由自家的服務(wù)器同微信的服務(wù)器交互,生成與支付訂單,返回給App客戶端的,這樣一來,比較安全,通知客戶端所需要做的事情也少了。
3.微信支付官方文檔(https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_1)
自己看。。剛開始看的時(shí)候一頭霧水,需要慢慢有耐心的看。。。
4.微信支付官方demo(https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=11_1)
集成微信支付的時(shí)候需要用到其中的類和方法。
二、支付流程(附上官方圖)
根據(jù)我的理解可以簡單的分為以下幾步
1.App客戶端將簡單的訂單信息發(fā)送給自己的服務(wù)器(訂單信息跟后臺商量,需要傳哪些參數(shù));
2.App服務(wù)端調(diào)用統(tǒng)一下單Api同微信服務(wù)端進(jìn)行交互,微信服務(wù)端生成與支付訂單(帶有prepay_id的訂單信息)返回給App服務(wù)端。
3.服務(wù)端將預(yù)支付訂單進(jìn)行加簽處理后返回給App客戶端,由App客戶端進(jìn)行支付。
4.App客戶端收到加簽后的預(yù)支付訂單后,調(diào)用微信SDK進(jìn)行支付,支付結(jié)果在WxPayEntryActivity中顯示。
三、支付中遇到的問題
1.提交訂單時(shí)顯示簽名錯(cuò)誤,原因可能是下單中的參數(shù)(body)中含有中文,需要進(jìn)行iso8859-1編碼。
2.微信服務(wù)端返回prepay_id后,進(jìn)行支付時(shí)返回簽名錯(cuò)誤,原因可能是沒有進(jìn)行注冊,wXapi.registerApp(WxConstans.APP_ID);
3.能夠進(jìn)行支付了,但是支付結(jié)果一直顯示-1,支付失敗,原因可能是
①App打包時(shí)必須用你申請支付時(shí)的簽名文件
②我將微信的緩存清空了,可以進(jìn)行一次支付,然后就不行了。難道要每次支付都要清空一次緩存么。。這樣肯定不行,包名也要與申請支付時(shí)的包名一致。
4.官方demo中用到了HttpClient這個(gè)類,然而在AS中已經(jīng)不適用這個(gè)類了,需要在module的build.gradle中添加
useLibrary 'org.apache.http.legacy'方可使用HttpClient。
開始進(jìn)行支付了!!!
我將所有的工作全部放在了客戶端完成,講道理加簽什么的應(yīng)該放在服務(wù)端的!!)
1.支付準(zhǔn)備
- APP_ID (App_id申請時(shí)候給的)
- MCH_ID (商戶號)
- API_KEY (API秘鑰)
- APP_notify_url (異步通知服務(wù)器地址,跟自家服務(wù)端商量)
- 微信的jar包(官方demo中有)
- dom4j-full.jar (解析xml使用的jar包)
2.構(gòu)造支付實(shí)體類
private String appid; //appid
private String body; //商品描述
private String mch_id; //商戶ID
private String nonce_str; //隨機(jī)字符串32位
private String notify_url; //微信通知后臺支付結(jié)果url
private String out_trade_no; //我們自己的訂單號,由自家服務(wù)端返回的唯一訂單號
private String spbill_create_ip; //客戶端IP (我感覺不寫也可以。。)
private int total_fee; //總的支付金額(單位是分!!!)
private String trade_type; //因?yàn)槭且苿?dòng)應(yīng)用 所以是APP(固定值)
private String sign; //以上所有參數(shù)的MD5簽名
3.注冊微信!!!!!!!!!!
在你需要進(jìn)行支付的Activiity中注冊微信,一般在onCreate方法中
private IWXAPI wXapi = WXAPIFactory.createWXAPI(this, null);
wXapi.registerApp(WxConstans.APP_ID);
4.構(gòu)造訂單的實(shí)體類
entity.setAppid(WxConstans.APP_ID);
entity.setBody("test");
entity.setMch_id(WxConstans.MCH_ID);
entity.setNonce_str(getNonceStr());//單獨(dú)一個(gè)方法獲取32位隨機(jī)數(shù)
entity.setNotify_url(WxConstans.APP_notify_url);
entity.setOut_trade_no(getOutTradNo());//單獨(dú)方法獲取唯一訂單號
entity.setTotal_fee(1);//1分錢
entity.setTrade_type("APP");//固定值
entity.setSpbill_create_ip("192.168.179.2");
//生成隨機(jī)號,防重發(fā)
private String getNonceStr() {
Random random = new Random();
return MD5.getMessageDigest(String.valueOf(random.nextInt(10000)).getBytes());
}
//生成訂單號
private String getOutTradNo() {
Random random = new Random();
return MD5.getMessageDigest(String.valueOf(random.nextInt(10000)).getBytes());
}
5.參考下單API開始拼單
//構(gòu)造商品參數(shù)集合,因?yàn)樾枰判颍杂玫搅薙ortedMap
SortedMap<Object, Object> parameters = new TreeMap<Object, Object>();
parameters.put("appid", entity.getAppid());
parameters.put("body", entity.getBody());
parameters.put("mch_id", entity.getMch_id());
parameters.put("nonce_str", entity.getNonce_str());
parameters.put("notify_url", entity.getNotify_url());
parameters.put("out_trade_no", entity.getOut_trade_no());
parameters.put("total_fee", entity.getTotal_fee());
parameters.put("trade_type", entity.getTrade_type());
parameters.put("spbill_create_ip", entity.getSpbill_create_ip());
//將訂單信息簽名后再傳入
parameters.put("sign", WxUtils.createSign("UTF-8", parameters, WxConstans.API_KEY));
/** * 微信支付簽名算法sign
* @param characterEncoding 簽名編碼(UTF-8)
* @param parameters 要簽名的參數(shù)的集合
* @param key 商戶自己設(shè)置的key
*/
public static String createSign(String characterEncoding, SortedMap<Object,Object> parameters, String key){
StringBuffer sb = new StringBuffer();
Set es = parameters.entrySet();//所有參與傳參的參數(shù)按照accsii排序(升序)
Iterator it = es.iterator();
while(it.hasNext()) {
Map.Entry entry = (Map.Entry)it.next();
String k = (String)entry.getKey();
Object v = entry.getValue();
if(null != v && !"".equals(v)&& !"sign".equals(k) && !"key".equals(k)) {
sb.append(k + "=" + v + "&");
}
}
sb.append("key=" + key);
System.out.println(sb.toString());
String sign = WxMd5.MD5Encode(sb.toString(), characterEncoding).toUpperCase();
System.out.println(sign);
return sign;}
public class WxMd5 {
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("utf-8")));
} 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"};
}
6.將訂單信息拼成xml格式
由于微信服務(wù)器只接收xml格式的數(shù)據(jù),需要將訂單信息拼成xml格式
//3.因?yàn)榻y(tǒng)一下單接口需要以xml格式post發(fā)送給微信,所以我們先拼接xml格式的參數(shù):
StringBuilder xmlBuilder = new StringBuilder();
xmlBuilder.append("<xml>");
Set es = parameters.entrySet();//所有參與傳參的參數(shù)按照accsii排序(升序)
Iterator it = es.iterator();while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
String k = (String) entry.getKey();
Object v1 = entry.getValue();
xmlBuilder.append("<").append(k).append(">");
xmlBuilder.append(v1);
xmlBuilder.append("</").append(k).append(">");}
xmlBuilder.append("</xml>");
System.out.println(xmlBuilder.toString());
Log.d("tag", "拼裝的xml信息" + xmlBuilder.toString());
try {
//異步線程獲取微信服務(wù)器返回的信息
new GetPrepayId(new String(xmlBuilder.toString().getBytes(), "ISO8859-1")).execute();//這一步非常重要,不這樣轉(zhuǎn)換編碼的話,傳遞中文就會(huì)報(bào)“簽名錯(cuò)誤”,這是很多人都會(huì)遇到的錯(cuò)誤。}
catch (Exception e) {
e.printStackTrace();}
//異步線程請求統(tǒng)一下單接口:
public class GetPrepayId extends AsyncTask {
String str;
public GetPrepayId(String str) {
this.str = str;
}
@Override
protected void onPreExecute() {
super.onPreExecute();
}
@Override
protected Object doInBackground(Object[] params) {
//微信給的下單接口
String url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
//Util類為微信demo中自帶的一個(gè)類,可以復(fù)制出來,自行使用
byte[] buf = Util.httpPost(url, str);
String content = new String(buf);
Log.d("tag", "微信服務(wù)器返回的xml為: " + content);
//將xml轉(zhuǎn)為map
Map<String, String> map = xmlToMap(content);
Log.d("tag", "xml轉(zhuǎn)為map: " + map.toString());
String nonceStr = getNonceStr();
//獲取時(shí)間戳
String timeStamp = String.valueOf(getTimeStamp());
//開始準(zhǔn)備付款
PayReq request = new PayReq();
request.appId = WxConstans.APP_ID;
request.partnerId = WxConstans.MCH_ID;
request.prepayId = map.get("prepay_id");
request.packageValue = "Sign=WXPay";
request.nonceStr = nonceStr;
request.timeStamp = timeStamp;
//再簽名
SortedMap<Object, Object> parameters = new TreeMap<Object, Object>();
parameters.put("appid", request.appId);
parameters.put("partnerid",request.partnerId);
parameters.put("prepayid", request.prepayId);
parameters.put("package", request.packageValue);
parameters.put("noncestr", request.nonceStr);
parameters.put("timestamp", request.timeStamp);
request.sign = WxUtils.createSign("UTF-8", parameters, WxConstans.API_KEY);
Log.d("tag", "request.sign" + request.sign);
//真開始支付了,支付結(jié)果在WXPayEntryActivity中顯示
wXapi.sendReq(request);
return content;
}}
xml轉(zhuǎn)map,需要用到dom4j-full.jar 包
//將xml轉(zhuǎn)為map,相信都能看懂,不做注釋。。。
public Map<String, String> xmlToMap(String xmlstr) {
Map<String, String> map = new HashMap<>();
try {
SAXReader reader = new SAXReader();
InputStream ins = new ByteArrayInputStream(xmlstr.getBytes("UTF-8"));
Document doc = reader.read(ins);
Element root = doc.getRootElement();
List<Element> list = root.elements();
for (Element e : list) {
map.put(e.getName(), e.getText());
}
ins.close();
} catch (Exception e) {
e.printStackTrace();
}
return map;}
獲取時(shí)間戳
public static long getTimeStamp() { return System.currentTimeMillis() / 1000;}
需要注意
WXPayEntryActivity必須在當(dāng)前包名.wxapi包下。
xml中聲明
<activity
android:name=".wxapi.WXPayEntryActivity"
android:exported="true"
android:launchMode="singleTop"/>