編輯:關於Android編程
本來是不打算寫關於微信支付的,網上有太多了,但是前些天一個哥們居然還問我微信支付怎麼集成,所以准備做個記錄。
以下內容是將微信支付的所有過程都放在在app端,這是不推薦的,實際中前幾步由服務器來做比較安全些。當然,我這樣寫一個是為了展示微信支付的一個流程,二是服務器端哥們不願寫,老大讓我全在客戶端完成(汗一個),廢話不多說了,進入正題。
我個人將集成微信支付的過程分成4個步驟: 微信官方api文檔:https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_1
1.配置各種信息
2.拼湊預訂單信息,訪問微信服務器生成預訂單,主要是為了得到prepared_id —– 建議在自己的服務器操作
3.根據得到的prepared_id及其他信息進行二次簽名,調起微信sdk支付 — 前部分建議在服務器操作,後面部分在app端操作
4.根據回調的支付結果執行不同的邏輯
接下來具體說說各個步驟
1、配置各種信息 如在支付Activity中PayActivity(名字自己定)和微信回調的WXPayEntryActivity(這個類的名字不允許改變,包名固定為你應用的包名+wxapi)功能清單文件配置
當然,權限什麼的就不說了,別忘了哈
2.拼湊預訂單信息,訪問微信服務器生成預訂單,主要是為了得到prepared_id,如下的代碼是在客戶端生成的實例,如果不需要在客戶端操作,請無視
生成預訂單有10個必選參數分別為 (注意參數名不能改變,post請求,以xml格式)
appid(應用id)、 mch_id(商戶號)、 nonce_str(隨機字符串,參照我下面的代碼生成)、 sign(簽名,參照我下面的代碼簽名)、 body(商品描述)、
out_trade_no(商戶訂單號,自己生成,唯一)、 total_fee(總金額,單位:分,不能像支付寶有0.01這種)、 spbill_create_ip(終端ip,參照下面ipv4的獲取)、 notify_url(微信支付結果異步通知的地址)、 trade_type(交易類型,app的填APP即可)
官方的參數示例
wx2421b1c4370ec43b
支付測試APP支付測試
10000100
1add1a30ac87aa2db72f57a2375d8fec
http://wxpay.weixin.qq.com/pub_v2/pay/notify.v2.php
1415659990
14.23.150.211
1
APP
0CB01533B8C1EF103065174F50BCA001
下面是幾個參數的生成:
隨機字符串的生成方式
/**
* 生成隨機字符串,算法自己考慮
* @return
*/
private String genNonceStr() {
Random random = new Random();
return MD5.getMessageDigest(String.valueOf(random.nextInt(10000)).getBytes());
}
訂單號的生成:
/**
* get the out_trade_no for an order. 生成商戶訂單號,該值在商戶端應保持唯一(可自定義格式規范)
*
*/
private String getWXOutTradeNo() {
SimpleDateFormat format = new SimpleDateFormat("MMddHHmmss", Locale.getDefault());
Date date = new Date();
String key = format.format(date);
Random r = new Random();
key = key + r.nextInt();
key = key.substring(0, 15);
return key;
}
ipv4的獲取方式:
/**
* 獲取自己手機的ipv4地址
* @return
*/
public String getIp() {
try {
for (Enumeration en = NetworkInterface
.getNetworkInterfaces(); en.hasMoreElements();) {
NetworkInterface intf = en.nextElement();
for (Enumeration ipAddr = intf.getInetAddresses(); ipAddr
.hasMoreElements();) {
InetAddress inetAddress = ipAddr.nextElement();
// ipv4地址
if (!inetAddress.isLoopbackAddress()
&& InetAddressUtils.isIPv4Address(inetAddress
.getHostAddress())) {
Log.e("TAG", "ipv4=" + inetAddress.getHostAddress());
return inetAddress.getHostAddress();
}
}
}
} catch (Exception ex) {
}
return "192.168.1.0";
}
簽名的生成:
/** * 生成package參數,就是簽名參數 * @param params * @return */ private String genPackage(Listparams) { 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(Constants.API_KEY); // 注意:不能hardcode在客戶端,建議genPackage這個過程都由服務器端完成 return MD5.getMessageDigest(sb.toString().getBytes()).toUpperCase(); }
生成xml字符串的方法:
/** * 生成xml文件字符串 * @param packageParams * @return */ private String toXml(ListpackageParams) { StringBuffer xml =new StringBuffer(""); xml.append(" "); for(int i = 0;i < packageParams.size();i++){ NameValuePair nameValuePair = packageParams.get(i); xml.append("<" + nameValuePair.getName() + ">" + nameValuePair.getValue() + ""); if(i == packageParams.size() - 1){ xml.append(" "); } } return new String(xml); }
接下來就可以將上面的10個參數生成xml文件字符串上傳
/**
* 生成預訂單的信息
* @return
*/
private String getProductArgs(){
String nonceStr = genNonceStr();//隨機字符串
List packageParams = new LinkedList();
packageParams
.add(new BasicNameValuePair("appid", Constants.APP_ID));
packageParams.add(new BasicNameValuePair("body", aliOrderBO.getName()));//商品名稱
// packageParams.add(new BasicNameValuePair("input_charset", "UTF-8"));
packageParams.add(new BasicNameValuePair("detail", aliOrderBO.getDescribe()));//商品詳情
packageParams
.add(new BasicNameValuePair("mch_id", Constants.MCH_ID));//商戶號
packageParams.add(new BasicNameValuePair("nonce_str", nonceStr));//隨機字符串
packageParams.add(new BasicNameValuePair("notify_url", UrlStrings.getUrl(UrlIds.WXPAY_NOTIFY)));//接收服務器異步結果的url
String orderCode = getWXOutTradeNo();//生成商戶訂單號
((MyApplication) getApplication()).setWxTradeNo(orderCode);//將訂單號保存
packageParams.add(new BasicNameValuePair("out_trade_no",//商戶訂單號
orderCode));
packageParams.add(new BasicNameValuePair("spbill_create_ip",getIp()));//用戶終端IP
double totalFee = aliOrderBO.getPrice()*100;//價格,單位是分
packageParams.add(new BasicNameValuePair("total_fee", String.valueOf((int)totalFee)));//訂單總金額
packageParams.add(new BasicNameValuePair("trade_type", "APP"));//交易類型
String packageValue = genPackage(packageParams);
packageParams.add(new BasicNameValuePair("sign", packageValue));
try {
return new String(toXml(packageParams).toString().getBytes(), "ISO8859-1");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return null;
}
上傳預訂單信息給微信服務器
String url = String.format("https://api.mch.weixin.qq.com/pay/unifiedorder");//微信預訂單的地址,請參考微信官方
new GetPrepayIdTask().execute(url, entry);//訪問微信服務器
//上傳的類
private class GetPrepayIdTask extends AsyncTask {
@Override
protected void onPreExecute() {
}
@Override
protected void onPostExecute(PrepareWXPayBean result) {//這個PrepareWXPayBean是服務器返回的結果,自定義的類
if (result.localRetCode == PrepareWXPayBean.LocalRetCode.ERR_OK) {
sendPayReq(result);
} else {
Log.e("TAG", "error");
}
}
@Override
protected void onCancelled() {
super.onCancelled();
}
@Override
protected PrepareWXPayBean doInBackground(String... params) {
PrepareWXPayBean result = new PrepareWXPayBean();
if (params == null || params.length <= 0) {
result.localRetCode = PrepareWXPayBean.LocalRetCode.ERR_ARGU;
return result;
}
byte[] buf = Util.httpPost(params[0],params[1]);
if (buf == null || buf.length == 0) {
result.localRetCode = PrepareWXPayBean.LocalRetCode.ERR_HTTP;
return result;
}
String content = new String(buf);
result = decodeXML(content);
result.localRetCode = PrepareWXPayBean.LocalRetCode.ERR_OK;
return result;
}
}
3.解析返回的預訂單信息再次簽名,然後調起微信sdk(前面建議的服務器完成,如果不需要,請無視)
返回結果示例(請求成功的結果,其他情況請參考api):
解析結果的方法(這只是個示例,具體方式請自行考慮):
/**
* 解析xml
* @param content
* @return
*/
private PrepareWXPayBean decodeXML(String content) {
XmlPullParser parser = Xml.newPullParser();
PrepareWXPayBean bean = new PrepareWXPayBean();
try {
parser.setInput(new StringReader(content));
int event = parser.getEventType();
while (event != XmlPullParser.END_DOCUMENT) {
switch (event) {
case XmlPullParser.START_DOCUMENT:
break;
case XmlPullParser.START_TAG:
if ("return_code".equals(parser.getName())) {
parser.next();
bean.setReturn_code(parser.getText());
} else if ("return_msg".equals(parser.getName())) {
parser.next();
bean.setReturn_msg(parser.getText());
}else if ("appid".equals(parser.getName())) {
parser.next();
bean.setAppid(parser.getText());
}else if ("mch_id".equals(parser.getName())) {
parser.next();
bean.setMch_id(parser.getText());
}else if ("nonce_str".equals(parser.getName())) {
parser.next();
bean.setNonce_str(parser.getText());
}else if ("sign".equals(parser.getName())) {
parser.next();
bean.setSign(parser.getText());
}else if ("result_code".equals(parser.getName())) {
parser.next();
bean.setResult_code(parser.getText());
}else if ("prepay_id".equals(parser.getName())) {
parser.next();
bean.setPrepay_id(parser.getText());
}else if ("trade_type".equals(parser.getName())) {
parser.next();
bean.setTrade_type(parser.getText());
}
break;
case XmlPullParser.END_TAG:
break;
}
event = parser.next();
}
} catch (Exception e) {
e.printStackTrace();
}
return bean;
}
解析完成後進行二次簽名,簽名方法如下 key就是api秘鑰 key設置路徑:微信商戶平台(pay.weixin.qq.com)–>賬戶設置–>API安全–>密鑰設置
private static final char SPLIT = '&';
/**
* 微信開放平台和商戶約定的密鑰
*
* 注意:不能hardcode在客戶端,建議genSign這個過程由服務器端完成
*/
private String genSign(PayReq req) {
StringBuilder sb = new StringBuilder();
sb.append("appid=");
sb.append(req.appId);
sb.append(SPLIT);
sb.append("noncestr=");
sb.append(req.nonceStr);
sb.append(SPLIT);
sb.append("package=");
sb.append(req.packageValue);
sb.append(SPLIT);
sb.append("partnerid=");
sb.append(req.partnerId);
sb.append(SPLIT);
sb.append("prepayid=");
sb.append(req.prepayId);
sb.append(SPLIT);
sb.append("timestamp=");
sb.append(req.timeStamp);
sb.append(SPLIT);
sb.append("key=");
sb.append(Constants.API_KEY); // 注意:不能hardcode在客戶端,建議genSign這個過程都由服務器端完成
return MD5.getMessageDigest(sb.toString().getBytes()).toUpperCase();
}
生成時間戳的方法:
/**
* 生成時間戳
* @return
*/
private String genTimeStamp() {
return String.valueOf(System.currentTimeMillis() / 1000);
}
然後就可以調起微信sdk支付啦
private void sendPayReq(PrepareWXPayBean result) {
if ("FAIL".equals(result.getResult_code())) {
Log.e(TAG, "sendPayReq fail, retCode = " + result.getResult_code() + ", retmsg = " + result.getReturn_msg());
return;
}
String prepare_id = result.getPrepay_id();
Log.d(TAG, "sendPayReq, prepare_id = " + prepare_id + ", sign = " + result.getSign());
if (prepare_id == null || prepare_id.length() == 0) {
Log.e(TAG, "sendPayReq fail, prepare_id is empty");
return;
}
PayReq req = new PayReq();
req.appId = result.getAppid();
req.partnerId = result.getMch_id();//商戶號
req.prepayId = prepare_id;
req.nonceStr = result.getNonce_str();//隨機字符串
req.timeStamp = genTimeStamp();//時間戳
req.packageValue = "Sign=WXPay";//固定字符串
req.sign = genSign(req);//簽名
//req.extData = "app data"; // optional,微信不處理該字段,會在PayResp結構體中回傳該字段
// 在支付之前,如果應用沒有注冊到微信,應該先調用IWXMsg.registerApp將應用注冊到微信
api.registerApp(Constants.APP_ID);
api.sendReq(req);
}
4.處理回調 再次強調這個類 微信回調的WXPayEntryActivity(這個類的名字不允許改變,包名固定為你應用的包名+wxapi
@Override
public void onResp(BaseResp baseResp) {
Log.d("TAG", "onPayFinish, errCode = " + baseResp.errCode);
//Log.e("TAG", "info=" + baseResp.errStr + ",transaction=" + baseResp.transaction + ",openId=" + baseResp.openId);
Bundle bundle = new Bundle();
if (baseResp.getType() == ConstantsAPI.COMMAND_PAY_BY_WX) {
if(baseResp.errCode == 0){
Toast.makeText(WXPayEntryActivity.this, "支付成功", Toast.LENGTH_SHORT).show();
}else {
Toast.makeText(WXPayEntryActivity.this, "支付失敗", Toast.LENGTH_SHORT).show();
}
}
最後再強調一個,微信sdk想要成功調起,必須用正式簽名,正式簽名,正式簽名。不只是支付sdk,分享和登錄的也是一樣
大體的流程就這樣了,小弟能力有限,如果有什麼錯誤歡迎指正!
Activity之間的動畫切換學習筆記(一)
首先什麼是Transition? 安卓5.0中Activity和Fragment變換是建立在名叫Transitions的安卓新特性之上的。這個誕生於4.4的transit
android launchmode 使用場景
菜鳥起飛記android launchmode 使用場景Activity一共有以下四種launchMode:1.standard2.singleTop3.singleTa
Android實現多媒體錄音筆
記事本涉及到的僅僅是對string 的存儲,而且在讀取上並不存在什麼難點,直接用textview顯示便可以了。需要做的主要是使用SQLite對數據進行一個整理。而錄音筆需
OpenglES2.0 for Android:來畫個三角形吧
先看看我們的整個流程: 理解坐標系: 左側是Opengl默認的坐標系,右邊是典型的android設備屏幕的坐標系。左側的瘦瘦的