這幾天給app對(duì)接了微信支付墅拭,官方的demo真是薇宠。。。第一次果然不免爬幾個(gè)坑陶因,記錄一下,避免遺忘垂蜗。
錯(cuò)誤說(shuō)明
- 在保證提供的參數(shù)都正確的情況下楷扬,往往測(cè)試微信支付失敗的原因是簽名錯(cuò)誤,這個(gè)時(shí)候應(yīng)該仔細(xì)核對(duì)流程贴见。就我自己而言碰到的問(wèn)題就是搞錯(cuò)了最后發(fā)送訂單的時(shí)候的簽名sign參數(shù)烘苹。這個(gè)簽名不是第一次組拼的簽名,也不是生成預(yù)支付交易單片部,獲取prepayId時(shí)返回的簽名镣衡,而是獲取到prepayId后用prepayId和其他參數(shù)第二次組拼成的sign。
流程說(shuō)明
- 詳細(xì)的業(yè)務(wù)流程為微信官方文檔里有档悠,我這邊以所有參數(shù)由客戶端提供的方式簡(jiǎn)單說(shuō)明一下請(qǐng)求流程廊鸥。
獲取必須參數(shù),第一次簽名辖所。
需要說(shuō)明的是簽名中的key參數(shù)是由我方自行在微信商戶平臺(tái)設(shè)置的惰说。具體位置:微信商戶平臺(tái)(pay.weixin.qq.com)-->賬戶設(shè)置-->API安全-->密鑰設(shè)置生成預(yù)支付交易單,獲取prepayId缘回。
調(diào)用微信的接口“https://api.mch.weixin.qq.com/pay/unifiedorder”獲取prepayId吆视。另外返回信息中的sign并不是最后調(diào)起支付中的sign參數(shù)。第二次簽名酥宴。
用"appid"啦吧,"noncestr","package"拙寡,"partnerid"授滓,"prepayid","timestamp"生成第二次簽名倒庵。簽名方式與第一次相同褒墨,參數(shù)不同(注意參數(shù)順序)。這樣就拿到了調(diào)起微信支付所需的所有參數(shù)擎宝。調(diào)起微信支付郁妈。
代碼說(shuō)明
public class WXPayUtils {
private Context mContext;
private PayReq req;
private IWXAPI iwxapi; //微信支付api
private Map<String, String> resultunifiedorder;
public WXPayUtils() {
}
public WXPayUtils(Context context) {
this.mContext = context;
req = new PayReq();
iwxapi = WXAPIFactory.createWXAPI(context, null); //初始化微信api
}
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 0: //獲取參數(shù)及調(diào)起支付
getPayParameter();
sendPayReq();
break;
default:
break;
}
}
};
/**
* 第一次簽名及生成prepayId
**/
public void toWXPayAndSign(int money) {
PrePayIdAsyncTask prePayIdAsyncTask = new PrePayIdAsyncTask();
prePayIdAsyncTask.execute(money);
}
private class PrePayIdAsyncTask extends AsyncTask<Integer, Void, Map<String, String>> {
@Override
protected void onPreExecute() {
// TODO Auto-generated method stub
super.onPreExecute();
}
@Override
protected Map<String, String> doInBackground(Integer... params) {
// TODO Auto-generated method stub
String urlString = "https://api.mch.weixin.qq.com/pay/unifiedorder";
int money = params[0];
String entity = getProductArgs(money);
Log.e("Simon", ">>>>" + entity);
byte[] buf = Util.httpPost(urlString, entity);
String content = new String(buf);
Log.e("orion", "----" + content);
Map<String, String> xml = decodeXml(content);
return xml;
}
@Override
protected void onPostExecute(Map<String, String> result) {
// TODO Auto-generated method stub
super.onPostExecute(result);
resultunifiedorder = result;
handler.sendEmptyMessage(0);
}
}
/**
* 生成xml參數(shù),用于生成預(yù)支付交易單绍申,獲取prepayId
* @param money
* @return
*/
private String getProductArgs(int money) {
StringBuffer xml = new StringBuffer();
try {
DecimalFormat df = new DecimalFormat("#");
String price = df.format(money * 100);
String nonceStr = getNonceStr();
xml.append("<xml>");
List<NameValuePair> packageParams = new LinkedList<NameValuePair>();
packageParams.add(new BasicNameValuePair("appid", WXKeys.WX_APPID));
packageParams.add(new BasicNameValuePair("body", "APP pay test"));
packageParams.add(new BasicNameValuePair("mch_id", WXKeys.WX_MCH_ID));
packageParams.add(new BasicNameValuePair("nonce_str", nonceStr));
packageParams.add(new BasicNameValuePair("notify_url", "http://www.baidu.com/"));//你自己的的回調(diào)地址X洹9苏谩!
packageParams.add(new BasicNameValuePair("out_trade_no", getOutTradeNo()));
packageParams.add(new BasicNameValuePair("total_fee", price));
packageParams.add(new BasicNameValuePair("trade_type", "APP"));
String sign = getPackageSign(packageParams);
packageParams.add(new BasicNameValuePair("sign", sign));
String xmlString = toXml(packageParams);
return xmlString;
} catch (Exception e) {
// TODO: handle exception
return null;
}
}
/**
* 獲取支付參數(shù)
*/
private void getPayParameter() {
req.appId = WXKeys.WX_APPID;
req.partnerId = WXKeys.WX_MCH_ID;
if (resultunifiedorder != null) {
req.prepayId = resultunifiedorder.get("prepay_id");
} else {
Toast.makeText(mContext, "預(yù)支付交易單生成失敗", Toast.LENGTH_SHORT).show();
}
req.packageValue = "Sign=WXPay";
req.nonceStr = getNonceStr();
req.timeStamp = String.valueOf(genTimeStamp());
List<NameValuePair> signParams = new LinkedList<NameValuePair>();
signParams.add(new BasicNameValuePair("appid", req.appId));
signParams.add(new BasicNameValuePair("noncestr", req.nonceStr));
signParams.add(new BasicNameValuePair("package", req.packageValue));
signParams.add(new BasicNameValuePair("partnerid", req.partnerId));
signParams.add(new BasicNameValuePair("prepayid", req.prepayId));
signParams.add(new BasicNameValuePair("timestamp", req.timeStamp));
req.sign = getPaySign(signParams);
}
/**
* 調(diào)起微信支付
*/
private void sendPayReq() {
iwxapi.registerApp(WXKeys.WX_APPID);
iwxapi.sendReq(req);
}
/**
* 生成隨機(jī)數(shù)
* @return
*/
private String getNonceStr() {
// TODO Auto-generated method stub
Random random = new Random();
return getMessageDigest(String.valueOf(random.nextInt(10000)).getBytes());
}
/**
* 生成第一次簽名
* @param params
* @return
*/
private String getPackageSign(List<NameValuePair> params) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < params.size(); i++) {
sb.append(params.get(i).getName());
sb.append('=');
sb.append(params.get(i).getValue());
sb.append('&');
}
sb.append("key=");
sb.append(WXKeys.WX_PRIVATE_KEY);
String packageSign = getMessageDigest(sb.toString().getBytes()).toUpperCase();
Log.e("Simon", ">>>>" + packageSign);
return packageSign;
}
/**
* 生成第二次簽名
* @param params
* @return
*/
private String getPaySign(List<NameValuePair> params) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < params.size(); i++) {
sb.append(params.get(i).getName());
sb.append('=');
sb.append(params.get(i).getValue());
sb.append('&');
}
sb.append("key=");
sb.append(WXKeys.WX_PRIVATE_KEY);
String appSign = getMessageDigest(sb.toString().getBytes());
Log.e("Simon","----"+appSign);
return appSign;
}
/**
* 獲取時(shí)間戳
*
* @return
*/
private static long genTimeStamp() {
return System.currentTimeMillis() / 1000;
}
/**
* 獲取設(shè)備ip地址
* @return
*/
private static String getIpAddressString() {
try {
for (Enumeration<NetworkInterface> enNetI = NetworkInterface
.getNetworkInterfaces(); enNetI.hasMoreElements(); ) {
NetworkInterface netI = enNetI.nextElement();
for (Enumeration<InetAddress> enumIpAddr = netI
.getInetAddresses(); enumIpAddr.hasMoreElements(); ) {
InetAddress inetAddress = enumIpAddr.nextElement();
if (inetAddress instanceof Inet4Address && !inetAddress.isLoopbackAddress()) {
return inetAddress.getHostAddress();
}
}
}
} catch (SocketException e) {
e.printStackTrace();
}
return "127.0.0.1";
}
/**
* 獲取外部訂單號(hào)
*/
String getOutTradeNo() {
SimpleDateFormat format = new SimpleDateFormat("MMddHHmmss");
Date date = new Date();
String strKey = format.format(date);
java.util.Random r = new java.util.Random();
strKey = strKey + r.nextInt();
strKey = strKey.substring(0, 15);
return strKey;
}
/**
* 轉(zhuǎn)換xml
* @param params
* @return
*/
private static String toXml(List<NameValuePair> params) {
StringBuilder sb = new StringBuilder();
sb.append("<xml>");
for (int i = 0; i < params.size(); i++) {
sb.append("<" + params.get(i).getName() + ">");
sb.append(params.get(i).getValue());
sb.append("</" + params.get(i).getName() + ">");
}
sb.append("</xml>");
Log.e("Simon", ">>>>" + sb.toString());
return sb.toString();
}
/**
* 轉(zhuǎn)換xml
* @param content
* @return
*/
private 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) {
//實(shí)例化student對(duì)象
xml.put(nodeName, parser.nextText());
}
break;
case XmlPullParser.END_TAG:
break;
}
event = parser.next();
}
return xml;
} catch (Exception e) {
Log.e("Simon", "----" + e.toString());
}
return null;
}
/**
* md5加密
*
* @param buffer
* @return
*/
private 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;
}
}
}
參考
大致的流程就如上面所說(shuō)胃碾,其他沒(méi)有提到的大多在官方demo中有涨享。需要詳細(xì)的過(guò)程可以參考下面兩個(gè)兩篇(感謝原作者):
Android微信支付詳解與Demo
詳細(xì)介紹Android開(kāi)發(fā)集成微信支付(二) --- 完整版本