手機的在線支付,被認為是2012年最看好的功能,我個人認為這也是移動互聯網較傳統互聯網將會大放光彩的一個功能。
人人有手機,人人攜帶手機,花錢買東西,不再需要取錢付現,不再需要回家上網銀,想買什么,掃描一下,或者搜索一下,然后下單,不找零,直接送到你家,這將是手機支付給我們帶來的全新交易體驗。
谷歌剛推出了谷歌錢包,這必是我們后面要使用的主要手段,但是鑒於當前國情,我覺得有必要介紹一下android手機集成支付寶功能。
1.下載官方架包和說明文檔
其實官方已經提供了安裝指南,下載地址:
https://b.alipay.com/order/productDetail.htm?productId=2012120700377310&tabId=4#ps-tabinfo-hash
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).執行
1
|
openssl genrsa -out rsa_private_key.pem 1024
|
生成rsa_private_key.pem文件。
(2).再執行
1
|
openssl rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem
|
生成rsa_public_key.pem 文件。
(3).在執行
1
|
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,因為這一段之前沒有截圖,現在弄好了又不好截圖,如果有不明白的地方請大家參考官方文檔。
錯誤提示 failure calling remote service
原因: 私鑰沒轉PKCS8
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代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
|
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的核心代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
|
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 Context
mContext;
public
AlixPay(Context mContext) {
mContext
= mContext;
}
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(
mContext
,
"提示"
,
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(mContext,
"提示"
, 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(mContext);
boolean
isMobile_spExist = mspHelper.detectMobile_sp();
if
(!isMobile_spExist)
return
;
if
(!checkInfo()) {
BaseHelper.showDialog(mContext,
"提示"
,
"缺少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, mContext);
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="
+
"\""
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方法就是支付的方法,最簡單的不設置的話,調用方法如下:
1
2
|
AlixPay alixPay =
new
AlixPay(SettingTabActivity.
this
);
alixPay.pay();
|
如果沒有安裝支付寶,它會提示你安裝,如果已經安裝,它直接讓你選擇付款:
這說明已經配置成功了。
然后可以刪掉那些示例java文件了: AlixDemo.java,ProductListAdapter.java,Products.java。
你也可以通過調整參數來修改訂單信息,如主題,價格等。
另外在BaseHelper的94行:
1
|
dialog.setOnCancelListener(
new
AlixDemo.AlixOnCancelListener( (Activity)context ) );
|
需要修改為:
1
|
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);
|
需要修改為:
1
2
|
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.小結
支付寶的集成比我想象的要復雜一些,比較麻煩,首先需要審核,然后代碼需要提取,所以寫出來與大家分享。
在做集成配置的時候,一定要仔細認真,一個地方出錯,可能要導致后面查錯查很長時間。
因為本人是先集成成功后才寫的這篇文章,難免會漏掉一些重要的細節或者步驟,如有不對,請留言指正。