手機的在線支付,被認為是2012年最看好的功能,我個人認為這也是移動互聯網較傳統互聯網將會大放光彩的一個功能。 人人有手機,人人攜帶手機,花錢買東西,不再需要取錢付現,不再需要回家上網銀,想買什么,掃描一下,或者搜索一下,然后下單,不找零,直接送到你家,這將是手機支付給我們帶來的全新交易體驗。 谷歌剛推出了谷歌錢包,這必是我們后面要使用的主要手段,但是鑒於當前國情,我覺得有必要介紹一下android手機集成支付寶功能。
1.下載官方架包和說明文檔 其實官方已經提供了安裝指南,下載地址: https://mobiless.alipay.com/product/product_down_load.htm?code=SECURITY_PAY 里面有有個pdf,詳細說明了說用指南,寫的比較詳細,可以重點參考。
下載下來,我們主要是用到Android(20120104)目錄下的alipay_plugin.jar和AppDemo/assets下的alipay_plugin223_0309.apk,這兩個文件是我們不能修改的支付寶api和安裝包。
2. 商戶簽約 現在的安全機制,都是這樣,客戶端需要先和服務端請求驗證后才能進行進一步操作,oauth也是如此。 打開https://ms.alipay.com/,登陸支付寶,點擊簽約入口,選擇"應用類產品",填寫並等待審核,獲取商戶ID和賬戶ID。 簽約的時候還要向需要提供實名認證和上傳應用,所以我建議先把應用做好了,最后再集成支付寶。
我大概等了1-2天審核,審核是失敗的,回復是應用類型啥的應該是"虛擬貨幣",我改成那個馬上自動就審核通過了。
3.密鑰配置 解壓openssl-0.9.8k_WIN32(RSA密鑰生成工具).zip,打開cmd,命令行進入openssl-0.9.8k_WIN32(RSA密鑰生成工具)\bin目錄下, (1).執行
openssl genrsa -out rsa_private_key.pem 1024
生成rsa_private_key.pem文件。 (2).再執行
openssl rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem
生成rsa_public_key.pem 文件。 (3).在執行
openssl pkcs8 -topk8 -inform PEM -in rsa_private_key.pem -outform PEM -nocrypt
將RSA私鑰轉換成 PKCS8 格式,去掉begin和end那兩行,把里面的內容拷貝出來,保存到某個txt中,如rsa_private_pkcs8_key.txt中(我好像沒用到這個)。 打開rsa_public_key.pem,即商戶的公鑰,復制到一個新的TXT中,刪除文件頭”-----BEGIN PUBLIC KEY-----“與文件尾”-----END PUBLIC KEY-----“還有空格、換行,變成一行字符串並保存該 TXT 文件,然后在網站的“我的商家服務”切換卡下的右邊點擊“密鑰管理”,然后有個"上傳商戶公鑰(RSA)"項,選擇上傳剛才的TXT文件. 好了,服務器配置OK,因為這一段之前沒有截圖,現在弄好了又不好截圖,如果有不明白的地方請大家參考官方文檔。
4.引用jar和包含安裝包 (1).新建android工程; (2).copy上面說的alipay_plugin.jar到工程的libs目錄下,並在java build path中通過Add External JARs找到並引用該jar; (3).copy上面說的alipay_plugin223_0309.apk安裝包到assets目錄下,后面配置路徑用到。
如果libs和assets目錄沒有,手動建立者兩個目錄。
5.調用代碼整理 這里我們要嚴重的參考文檔中AppDemo,我們建一個包com.tianxia.lib.baseworld.alipay,把AppDemo的com.alipay.android.appDemo4包下的源碼全部copy到剛才我們自己的包下,還有res目錄下的資源文件也合並到我們工程res下。 其中AlixDemo.java,ProductListAdapter.java,Products.java是示例類,我們借鑒完后可以刪除。 PartnerConfig.java是配置類,配置商戶的一些配置參數。 其他的類是嚴重參考類,直接留下使用。 PartnerConfig.java代碼如下:
public class PartnerConfig { //合作商戶ID。用簽約支付寶賬號登錄ms.alipay.com后,在賬戶信息頁面獲取。 public static final String PARTNER = "xxx"; //賬戶ID。用簽約支付寶賬號登錄ms.alipay.com后,在賬戶信息頁面獲取。 public static final String SELLER = "xxx"; //商戶(RSA)私鑰 ,即rsa_private_key.pem中去掉首行,最后一行,空格和換行最后拼成一行的字符串 public static final String RSA_PRIVATE = "xxx"; //支付寶(RSA)公鑰 用簽約支付寶賬號登錄ms.alipay.com后,在密鑰管理頁面獲取。 public static final String RSA_ALIPAY_PUBLIC = "xxx"; //下面的配置告訴應用去assets目錄下找安裝包 public static final String ALIPAY_PLUGIN_NAME ="alipay_plugin223_0309.apk"; }
AlixDemo中代碼是最終的調用代碼在onItemClick(AdapterView<?> arg0, View arg1, int arg2, long arg3) {}中,下面我們提取其中的核心代碼。
6.提取核心調用代碼 在AlixDemo.java同目錄下新建AlixPay.java,來提取AlixDemo.java的核心代碼:
package com.tianxia.lib.baseworld.alipay; import java.net.URLEncoder; import java.text.SimpleDateFormat; import java.util.Date; import com.tianxia.lib.baseworld.R; import android.app.Activity; import android.app.ProgressDialog; import android.content.DialogInterface; import android.os.Handler; import android.os.Message; import android.view.KeyEvent; import android.widget.Toast; public class AlixPay { static String TAG = "AlixPay"; private Activity mActivity; public AlixPay(Activity activity) { mActivity = activity; } private ProgressDialog mProgress = null; // the handler use to receive the pay result. private Handler mHandler = new Handler() { public void handleMessage(Message msg) { try { String strRet = (String) msg.obj; switch (msg.what) { case AlixId.RQF_PAY: { closeProgress(); BaseHelper.log(TAG, strRet); try { String memo = "memo="; int imemoStart = strRet.indexOf("memo="); imemoStart += memo.length(); int imemoEnd = strRet.indexOf(";result="); memo = strRet.substring(imemoStart, imemoEnd); ResultChecker resultChecker = new ResultChecker(strRet); int retVal = resultChecker.checkSign(); if (retVal == ResultChecker.RESULT_CHECK_SIGN_FAILED) { BaseHelper.showDialog( mActivity, "提示", mActivity.getResources().getString( R.string.check_sign_failed), android.R.drawable.ic_dialog_alert); } else { BaseHelper.showDialog(mActivity, "提示", memo, R.drawable.infoicon); } } catch (Exception e) { e.printStackTrace(); BaseHelper.showDialog(mActivity, "提示", strRet, R.drawable.infoicon); } } break; } super.handleMessage(msg); } catch (Exception e) { e.printStackTrace(); } } }; // close the progress bar void closeProgress() { try { if (mProgress != null) { mProgress.dismiss(); mProgress = null; } } catch (Exception e) { e.printStackTrace(); } } public void pay() { MobileSecurePayHelper mspHelper = new MobileSecurePayHelper(mActivity); boolean isMobile_spExist = mspHelper.detectMobile_sp(); if (!isMobile_spExist) return; if (!checkInfo()) { BaseHelper.showDialog(mActivity, "提示", "缺少partner或者seller,", R.drawable.infoicon); return; } try { // prepare the order info. String orderInfo = getOrderInfo(); String signType = getSignType(); String strsign = sign(signType, orderInfo); strsign = URLEncoder.encode(strsign); String info = orderInfo + "&sign=" + "\"" + strsign + "\"" + "&" + getSignType(); // start the pay. MobileSecurePayer msp = new MobileSecurePayer(); boolean bRet = msp.pay(info, mHandler, AlixId.RQF_PAY, mActivity); if (bRet) { // show the progress bar to indicate that we have started paying. closeProgress(); mProgress = BaseHelper.showProgress(mActivity, null, "正在支付", false, true); } else ; } catch (Exception ex) { Toast.makeText(mActivity, R.string.remote_call_failed, Toast.LENGTH_SHORT).show(); } } private boolean checkInfo() { String partner = PartnerConfig.PARTNER; String seller = PartnerConfig.SELLER; if (partner == null || partner.length() <= 0 || seller == null || seller.length() <= 0) return false; return true; } // get the selected order info for pay. String getOrderInfo() { String strOrderInfo = "partner=" + "\"" + PartnerConfig.PARTNER + "\""; strOrderInfo += "&"; strOrderInfo += "seller=" + "\"" + PartnerConfig.SELLER + "\""; strOrderInfo += "&"; strOrderInfo += "out_trade_no=" + "\"" + getOutTradeNo() + "\""; strOrderInfo += "&"; //這筆交易價錢 strOrderInfo += "subject=" + "\"" + mActivity.getString(R.string.donate_subject) + "\""; strOrderInfo += "&"; //這筆交易內容 strOrderInfo += "body=" + "\"" + mActivity.getString(R.string.donate_body) + "\""; strOrderInfo += "&"; //這筆交易價錢 strOrderInfo += "total_fee=" + "\"" + "10.00" + "\""; strOrderInfo += "&"; strOrderInfo += "notify_url=" + "\"" + "http://notify.java.jpxx.org/index.jsp" + "\""; return strOrderInfo; } // get the out_trade_no for an order. 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; } // get the sign type we use. String getSignType() { String getSignType = "sign_type=" + "\"" + "RSA" + "\""; return getSignType; } // sign the order info. String sign(String signType, String content) { return Rsa.sign(content, PartnerConfig.RSA_PRIVATE); } // the OnCancelListener for lephone platform. static class AlixOnCancelListener implements DialogInterface.OnCancelListener { Activity mcontext; AlixOnCancelListener(Activity context) { mcontext = context; } public void onCancel(DialogInterface dialog) { mcontext.onKeyDown(KeyEvent.KEYCODE_BACK, null); } } }
這個類的pay方法就是支付的方法,最簡單的不設置的話,調用方法如下:
AlixPay alixPay = new AlixPay(SettingTabActivity.this); alixPay.pay();
如果沒有安裝支付寶,它會提示你安裝,如果已經安裝,它直接讓你選擇付款:
這說明已經配置成功了。 然后可以刪掉那些示例java文件了: AlixDemo.java,ProductListAdapter.java,Products.java。 你也可以通過調整參數來修改訂單信息,如主題,價格等。 另外在BaseHelper的94行:
dialog.setOnCancelListener( new AlixDemo.AlixOnCancelListener( (Activity)context ) );
需要修改為:
dialog.setOnCancelListener( new AlixPay.AlixOnCancelListener( (Activity)context ) );
7.注意 我在測試的時候,調用的activity是框在一個ActivityGroup里的(與tabhost類似,據說tabhost也有這個問題),導致MobileSecurePayer.java的pay方法中調用服務的兩行代碼:
mActivity.bindService(new Intent(IAlixPay.class.getName()), mAlixPayConnection, Context.BIND_AUTO_CREATE); mActivity.unbindService(mAlixPayConnection);
需要修改為:
mActivity.getApplicationContext().bindService(new Intent(IAlixPay.class.getName()), mAlixPayConnection, Context.BIND_AUTO_CREATE); mActivity.getApplicationContext().unbindService(mAlixPayConnection);
不然會報錯java.lang.ClassCastException: android.os.BinderProxy cannot be cast to com.android.server.am.ActivityRecord$Token...
8.小結 支付寶的集成比我想象的要復雜一些,比較麻煩,首先需要審核,然后代碼需要提取,所以寫出來與大家分享。 在做集成配置的時候,一定要仔細認真,一個地方出錯,可能要導致后面查錯查很長時間。 因為本人是先集成成功后才寫的這篇文章,難免會漏掉一些重要的細節或者步驟,如有不對,請留言指正。