最近項目裡面需要支付功能,boss一致決定用微信支付,所以在網上查了很多資料,說的不全,完了就找以前的同事指教。算是成功集成上去了。在這裡做個總結記錄。
1、在APP上集成微信支付,首先當然是當官網上去注冊並獲取到支付功能。這些不涉及到開發,官網上說的很詳細,這裡就不多做文章。獲取到這些能力了就為開發提供了條件了。開發中會用到的就是平台給的APPID、APPsercet、以及商戶平台上設置的APP_key。
2、具備了支付能力等前提條件之後,就是開發過程了。代碼裡面怎麼才能吊起支付了,參照官網上的DEMO自己也做了一些總結和各方大神的指教。分為了下面幾個步奏。
(1)、首先當然是將sdk配置進工程環境中,官網中下載Android端SDK,解壓後將libmmsdk.jar導入工程,然後將DEMO中Constant.java(這裡是參照官方demo的樣式寫的,當然也可以采用其他方式)、MD5.java、Util.java放入工程(我這裡用到了這些),這些先決條件有了之後就可以下一步寫代碼操作了。
(2)、生成訂單信息
生成訂單信息采用了如下方法生成,生成訂單信息需要簽名文件,所以裡面包含了生成簽名。微信要求所有請求采用XML參數形式,所有生產訂單信息之後又需要轉換成xml。訂單信息需要的請求參數可以到官網上去對照,這裡只加入了一些必要的參數。
生成訂單信息方法:
[java]view plaincopy
- //獲取產品訂單信息
- privateStringgenProductArgs(){
- StringBufferxml=newStringBuffer();
- try{
- StringnonceStr=genNonceStr();
-
- xml.append("");
- ListpackageParams=newLinkedList();
-
- packageParams.add(newBasicNameValuePair("appid",Constants.APP_ID));//APPID
-
- packageParams.add(newBasicNameValuePair("body","單價:"+singlePrice+"x"+payment_num.getText().toString()+"份"));//簡單描述
-
- packageParams.add(newBasicNameValuePair("mch_id",Constants.MCH_ID));//商戶ID
-
- packageParams.add(newBasicNameValuePair("nonce_str",nonceStr));//隨機字符串
-
- packageParams.add(newBasicNameValuePair("notify_url","http://www.weixin.qq.com/wxpay/pay.php"));//通知地址
-
- packageParams.add(newBasicNameValuePair("out_trade_no",getTrade()));//商戶訂單號
-
- packageParams.add(newBasicNameValuePair("spbill_create_ip",getLocalHostIp()));//終端IP
-
- //doubleprice=Double.parseDouble(payment_num.getText().toString())*(Integer.parseInt(singlePrice)*100);
- doubleprice=Double.parseDouble(singlePrice)*100*n;
- intpriceInt=(int)price;
- packageParams.add(newBasicNameValuePair("total_fee",priceInt+""));//微信接收int型價格
-
- packageParams.add(newBasicNameValuePair("trade_type","APP"));//支付類型
-
- Stringsign=genAppSign(packageParams);
- packageParams.add(newBasicNameValuePair("sign",sign));//簽名
-
- Stringxmlstring=parseNodeToXML(packageParams);//轉化成xml
-
- returnxmlstring;
- }catch(Exceptione){
- e.printStackTrace();
- returnnull;
- }
- }
構造這個xml請求參數采用的是httpclient生成的,所以引入了某些包。也可以采用其他方式生成支付訂單,只要最後的形式與官網中的形式相同即可。
裡面涉及到某些參數的生成,這裡列出的是我們項目裡面的業務邏輯,當然不同項目可定是不同的。
[java]view plaincopy
- //獲取訂單號
- privateStringgetTrade(){
- longnowTime=System.currentTimeMillis();
- SimpleDateFormatformat=newSimpleDateFormat("yyMMddHHmmss");
- returnformat.format(newDate(nowTime));
- }
-
-
-
- //獲取支付簽名Sign
- StringBuildersb=newStringBuilder();
- privateStringgenAppSign(Listparams){
- StringBuildersb=newStringBuilder();
- for(inti=0;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);
- this.sb.append("signstr\n"+sb.toString()+"\n\n");
- StringappSign=MD5.getMessageDigest(sb.toString().getBytes()).toUpperCase();
- returnappSign;
- }
-
- //獲取隨機字符串
- privateStringgenNonceStr(){
- Randomrandom=newRandom();
- returnMD5.getMessageDigest(String.valueOf(random.nextInt(10000)).getBytes());
- }
-
- /**
- *解析為xml格式
- *@paramtreeNodes
- *@return
- */
- publicStringparseNodeToXML(ListtreeNodes){
- StringBufferxmlnodes=newStringBuffer();
- if(treeNodes!=null&&treeNodes.size()>0){
- xmlnodes.append("");
- for(inti=0;i NameValuePairnode=treeNodes.get(i);
- xmlnodes.append("<"+node.getName()+">").append(node.getValue()).append("");
- }
- xmlnodes.append("");
- }
- //returnxmlnodes.toString();
- Stringxml=xmlnodes.toString();
- try{
- xml=newString(xml.toString().getBytes(),"ISO8859-1");//商品詳情為中文,將其轉化為統一編碼,不然獲取perpred_id失敗
- returnxml;
- }catch(UnsupportedEncodingExceptione){
- e.printStackTrace();
- returnnull;
- }
- }
-
-
- //獲取手機IP
- publicStringgetLocalHostIp(){
- Stringipaddress="";
- try{
- Enumerationen=NetworkInterface.getNetworkInterfaces();
- //遍歷所用的網絡接口
- while(en.hasMoreElements()){
- NetworkInterfacenif=en.nextElement();//得到每一個網絡接口綁定的所有ip
- Enumerationinet=nif.getInetAddresses();
- //遍歷每一個接口綁定的所有ip
- while(inet.hasMoreElements()){
- InetAddressip=inet.nextElement();
- if(!ip.isLoopbackAddress()&&InetAddressUtils.isIPv4Address(ip.getHostAddress())){
- returnip.getHostAddress();
- }
- }
- }
- }
- catch(SocketExceptione){
- Log.e("feige","獲取本地ip地址失敗");
- e.printStackTrace();
- }
- returnipaddress;
- }
(3)、訪問微信後台指定接口,獲取perpay_id。
可以說前面的都是為了獲取這個perpay_id做准備的,官網上給出的指定接口是“https://api.mch.weixin.qq.com/pay/unifiedorder” 請求采用官網demo中util提供的請求方式
這裡采用異步處理方式,當請求指定接口得到perapy_id之後直接吊起支付的方式。
[html]view plaincopy
- //調用支付獲取id
- publicvoidgotoWechat(){
- newAsyncTask(){
- @Override
- protectedObjectdoInBackground(Object[]objects){//獲取Prepay_id
- Stringurl=String.format("https://api.mch.weixin.qq.com/pay/unifiedorder");
- Stringentity=genProductArgs();//獲取訂單信息
- byte[]buf=Util.httpPost(url,entity);
- Stringcontent=newString(buf);//請求成功返回的信息
- //Log.e("orion",content);
- try{
- xmlParseTest(content);//解析返回的信息
- }catch(IOExceptione){
- e.printStackTrace();
- }catch(XmlPullParserExceptione){
- e.printStackTrace();
- }
- returnnull;
- }
-
- @Override
- protectedvoidonPostExecute(Objecto){
- super.onPostExecute(o);
- wechatPay();
- }
- }.execute();
- }
請求成功返回的數據當然也是xml格式的,需要解析並從中取到perpay_id(返回的結果不止perpay_id,包括其他信息,個人感覺吊起支付只需要perpay_id就行了)。這裡也是參考demo中的方式將獲取到的信息通過xml解析到
WeixinParentId對象當中。
[java] view plain copy
- /**
- *解析xml
- *返回prepay_id
- *通過對象Books獲取數據
- */
- WeixinParentIdbook=null;//通過對象Books獲取數據
- publicvoidxmlParseTest(Stringstr)throwsIOException,XmlPullParserException{
- XmlPullParserpullParser=Xml.newPullParser();//獲取XmlPullParser對象
- //InputStreamis=getContext().getAssets().open("parse.xml");//解析文本
- ByteArrayInputStreamis=newByteArrayInputStream(str.getBytes("UTF-8"));
- ArrayListbooks=null;
-
- pullParser.setInput(is,"UTF-8");
- inttype=pullParser.getEventType();//獲取事件類型
- while(type!=pullParser.END_DOCUMENT){//結束文本
- switch(type){
- caseXmlPullParser.START_DOCUMENT://開始文本
- books=newArrayList();
- break;
- caseXmlPullParser.START_TAG://開始標記
- if(pullParser.getName().equals("xml")){
- book=newWeixinParentId();
- }elseif(pullParser.getName().equals("return_msg")){
- type=pullParser.next();//指向下一個位置,不然無法獲取數據
- book.setReturn_msg(pullParser.getText());
- }elseif(pullParser.getName().equals("appid")){
- type=pullParser.next();
- book.setAppid(pullParser.getText());
- }elseif(pullParser.getName().equals("prepay_id")){
- type=pullParser.next();
- book.setPrepay_id(pullParser.getText());
- }
-
- break;
- caseXmlPullParser.END_TAG://結束標記
- if(pullParser.getName().equals("book")){
- books.add(book);
- book=null;//置為空釋放資源
- }
- break;
- }
- type=pullParser.next();//指向下一個標記
-
- }
- //Log.e("test","book------id----"+book.getPrepay_id());
- } 成功執行這不之後,book對象中perpay_id已經被賦值。只需在異步中取出使用即可。
[java] view plain copy
- //獲取到perpay_id之後吊起微信支付
- protectedvoidwechatPay(){
- PayReqreq=newPayReq();
- req.appId=Constants.APP_ID;
- req.partnerId=Constants.MCH_ID;
- req.prepayId=book.getPrepay_id();
- req.packageValue="Sign=WXPay";
- req.nonceStr=genNonceStr();
- req.timeStamp=String.valueOf(genTimeStamp());
-
- ListsignParams=newLinkedList();
- signParams.add(newBasicNameValuePair("appid",req.appId));
- signParams.add(newBasicNameValuePair("noncestr",req.nonceStr));
- signParams.add(newBasicNameValuePair("package",req.packageValue));
- signParams.add(newBasicNameValuePair("partnerid",req.partnerId));
- signParams.add(newBasicNameValuePair("prepayid",req.prepayId));
- signParams.add(newBasicNameValuePair("timestamp",req.timeStamp));
- req.sign=genAppSign(signParams);
- sb.append("sign\n"+req.sign+"\n\n");
- //在支付之前,如果應用沒有注冊到微信,應該先調用IWXMsg.registerApp將應用注冊到微信
- //Log.e("test","book.getPrepay_id()----------"+book.getPrepay_id()+"-------genNonceStr()-------"+genNonceStr()+"--------genTimeStamp()-------"+genTimeStamp()+"---genAppSign(signParams)--"+genAppSign(signParams));
- api.sendReq(req);
- //dialog.dismiss();
-
- }
-
- //獲取時間搓
- privatelonggenTimeStamp(){
- returnSystem.currentTimeMillis()/1000;
- }
把前面的異步操作方法賦給一個按鈕點擊事件,如果所有步奏正確,就可以進入支付界面了,如下圖:
點擊確支付當然就是輸入密碼什麼的操作了,支付成功後有一個反饋信息如下圖:
點擊完成,當然是回到APP咯,這裡就是微信提供的一個回調了。也就是官網上強調的wxapi包下的
WXPayEntryActivity裡的Onresp()方法中做回調處理,該包必須在項目工程包目錄下才能回調成功。下面貼出的是示例工程結構
在回調中彈出了對話框提示用戶支付成功並處理其他邏輯。
[java]view plaincopy
- publicclassWXPayEntryActivityextendsActivityimplementsIWXAPIEventHandler{
-
- privatestaticfinalStringTAG="MicroMsg.SDKSample.WXPayEntryActivity";
-
- privateIWXAPIapi;
-
-
- @OverridepublicvoidonCreate(BundlesavedInstanceState){
- super.onCreate(savedInstanceState);
-
- //setContentView(R.layout.activity_main2);
- api=WXAPIFactory.createWXAPI(this,Constants.APP_ID);
- api.handleIntent(getIntent(),this);
- api.registerApp(Constants.APP_ID);
- }
-
-
- @Override
- protectedvoidonNewIntent(Intentintent){
- super.onNewIntent(intent);
- setIntent(intent);
- api.handleIntent(intent,this);
- }
-
-
- @Override
- publicvoidonReq(BaseReqreq){
-
- }
-
-
- @Override
- publicvoidonResp(BaseRespresp){
- interrCode=resp.errCode;
-
- if(errCode==0){
-
- //0成功展示成功頁面
- //Intentintent=newIntent("name");
- //sendBroadcast(intent);
- //Log.e("test","支付成功的回調方法--onResp--");
- //Toast.makeText(this,"支付完成",Toast.LENGTH_SHORT).show();
-
- newAlertDialog.Builder(this).setMessage("支付成功").setPositiveButton("確定",newDialogInterface.OnClickListener(){
- @Override
- publicvoidonClick(DialogInterfacedialog,intwhich){
- dialog.dismiss();
- finish();
- PaymentActivity.instance.finish();
-
- Intentintent=newIntent(WXPayEntryActivity.this,PuzzGameActivity.class);
- intent.putExtra("ISPLAY",true);
- startActivity(intent);
-
- }
- }).setTitle("提示").create().show();
-
- Toast.makeText(this,"點擊確定按鈕開始參與拼圖游戲活動",Toast.LENGTH_LONG).show();
-
- }
- elseif(errCode==-1){
- //-1錯誤可能的原因:簽名錯誤、未注冊APPID、項目設置APPID不正確、注冊的APPID與設置的不匹配、其他異常等。
- newAlertDialog.Builder(this).setMessage("支付出錯").setPositiveButton("確定",newDialogInterface.OnClickListener(){
- @Override
- publicvoidonClick(DialogInterfacedialog,intwhich){
- dialog.dismiss();
- finish();
- }
- }).setTitle("提示").create().show();
- finish();
- }
- elseif(errCode==-2){
- //-2用戶取消無需處理。發生場景:用戶不支付了,點擊取消,返回APP。
- finish();
- }
- }
-
-
- }
這樣所有步驟就幾乎完全了,結合官方示例和文檔,應該可以快速的在項目中加入支付功能了。
當然,這裡所涉及到的步驟全是在app客戶端進行的,官網上建議將獲取簽名等操作放在服務後台進行,應該是為了安全吧,也就是客服端將訂單信息傳給服務端。服務端返回吊起支付必要的信息
(包括perpay_id、商戶id 、簽名等),然後由客戶端吊起微信支付的。