對於一個iOS的APP,如果有一些虛擬的商品或者服務需要通過在線支付來收費的話,一般有幾種主流的選擇。
如果是通過APP調用支付平臺APP的思路的話,一個是調起支付寶客戶端,一個則是調起微信支付。
實際上,從代碼的角度,調起支付APP就是把一些關鍵的參數通過一定方式打包成爲一個訂單,然後發送到支付平臺的服務器。所以,只要搞清楚了參數設置,搞清楚了每個支付平臺的SDK裏面一些關鍵API的使用,基本上就可以很簡單的支持支付。
今天記錄一下客戶端裏面,如何支持微信支付。首先。我們要仔細閱讀一下微信SDK的開發文檔,瞭解一下整個支付的大概流程。
然後根據提示,把相應的SDK下載下來,所謂的SDK,也就是一個鏈接庫和兩個頭文件,很簡單。
下載完畢,需要把SDK導入到工程裏面,並且配置一下工程。因爲開發者文檔已經有詳細描述,這裏就不再複述。
從文檔看到,調起微信支付其實最核心的是一下這麼一段
1
2
3
4
5
6
7
8
|
<code
class = "hljs"
vbscript= "" >PayReq
*request = [[[PayReq alloc] init] autorelease]; request.partnerId
= @10000100 ; request.prepayId=
@1101000000140415649af9fc314aa427 ; request. package
= @Sign =WXPay; request.nonceStr=
@a462b76e7436e98e0ed6e13c64b4fd1c ; request.timeStamp=
@1397527777 ; request.sign=
@582282D72DD2B03AD892830965F428CB16E7A256 ; [WXApi
sendReq:request];</code> |
這裏的範例是一段hardcode,真正使用的時候,參數都需要自行傳入。
爲了搞清楚如何使用API,我們可以下載Sample代碼。不過,這個sample代碼應該是微信的實習生寫的,而且應該是一個對於C++比較熟悉,對於ObjectC比較陌生的實習生。。。代碼風格可以看出很多東西哈。。所以這個sample讀起來總覺得有點奇怪。當然,寫出這個demo也是需要不錯的水平,因爲這個sample不僅僅是一些API的調用,還包括了一些算法的實現,MD5之類的。
看懂了sample之後,一般可以自己重構一下,成爲自己APP裏面的一個Manager類。
我是在2015 5 23下載的微信Sampel代碼,裏面包括有:
ApiXml.h
ApiXml.m
WXUtil.h
WXUtil.m
payRequestHandler.h
payRequestHandler.m
如果比較看重命名規範的OC程序猿,就會覺得這個payRequestHandler類非常彆扭,不符合camel命名規則,而且handler這個詞更偏向於c++風格。我就以這個類爲原型,重構了一下,並改裝成一個傳參的方法,供自己的APP調用。APP裏面賣商品,一般就是商品名字,價格兩個關鍵參數。所以這個重構的方法也只是提供這兩個參數的接口。
ApiXml.h && ApiXml.m && WXUtil.h && WXUtil.m不變
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
|
<code
class = "hljs"
objectivec= "" > // //
WechatPayManager.h // //
Created by HuangCharlie on 5/24/15. // // # import
<foundation foundation.h= "" > # import
WXUtil.h # import
ApiXml.h # import
WXApi.h //
賬號帳戶資料 //
更改商戶把相關參數後可測試 #define
APP_ID @wx @@@@@@@@@@@@@@@@
//APPID #define
APP_SECRET @ //appsecret,看起來好像沒用 //商戶號,填寫商戶對應參數 #define
MCH_ID @@@@@@@@@@@ //商戶API密鑰,填寫相應參數 #define
PARTNER_ID @12345678901234567890123456789012 //支付結果回調頁面 #define
NOTIFY_URL @http : //wxpay.weixin.qq.com/pub_v2/pay/notify.v2.php //獲取服務器端支付數據地址(商戶自定義)(在小吉這裏,簽名算法直接放在APP端,故不需要自定義) #define
SP_URL @http : //wxpay.weixin.qq.com/pub_v2/app/app_pay.php @interface
WechatPayManager : NSObject { } //預支付網關url地址 @property
(nonatomic,strong) NSString* payUrl; //debug信息 @property
(nonatomic,strong) NSMutableString *debugInfo; @property
(nonatomic,assign) NSInteger lastErrCode; //返回的錯誤碼 //商戶關鍵信息 @property
(nonatomic,strong) NSString *appId,*mchId,*spKey; //初始化函數 -(id)initWithAppID:(NSString*)appID mchID:(NSString*)mchID spKey:(NSString*)key; //獲取當前的debug信息 -(NSString
*) getDebugInfo; //獲取預支付訂單信息(核心是一個prepayID) -
(NSMutableDictionary*)getPrepayWithOrderName:(NSString*)name price:(NSString*)price device:(NSString*)device; @end </foundation></code> |
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
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
|
<code
class = "hljs"
objectivec= "" > // //
WechatPayManager.m // //
Created by HuangCharlie on 5/24/15. // // # import
WechatPayManager.h @implementation
WechatPayManager //初始化函數 -(id)initWithAppID:(NSString*)appID
mchID:(NSString*)mchID spKey:(NSString*)key { self
= [ super
init]; if (self) { //初始化私有參數,主要是一些和商戶有關的參數 self.payUrl
= @https : //api.mch.weixin.qq.com/pay/unifiedorder; if
(self.debugInfo == nil){ self.debugInfo
= [NSMutableString string]; } [self.debugInfo
setString:@]; self.appId
= appID; //微信分配給商戶的appID self.mchId
= mchID; // self.spKey
= key; //商戶的密鑰 } return
self; } //獲取debug信息 -(NSString*)
getDebugInfo { NSString
*res = [NSString stringWithString:self.debugInfo]; [self.debugInfo
setString:@]; return
res; } //創建package簽名 -(NSString*)
createMd5Sign:(NSMutableDictionary*)dict { NSMutableString
*contentString =[NSMutableString string]; NSArray
*keys = [dict allKeys]; //按字母順序排序 NSArray
*sortedArray = [keys sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) { return
[obj1 compare:obj2 options:NSNumericSearch]; }]; //拼接字符串 for
(NSString *categoryId in sortedArray) { if
( ![[dict objectForKey:categoryId] isEqualToString:@] &&
![categoryId isEqualToString: @sign ] &&
![categoryId isEqualToString: @key ] ) { [contentString
appendFormat:@%@=%@&, categoryId, [dict objectForKey:categoryId]]; } } //添加key字段 [contentString
appendFormat: @key =%@,
self.spKey]; //得到MD5
sign簽名 NSString
*md5Sign =[WXUtil md5:contentString]; //輸出Debug
Info [self.debugInfo
appendFormat: @MD5 簽名字符串: %@ ,contentString]; return
md5Sign; } //獲取package帶參數的簽名包 -(NSString
*)genPackage:(NSMutableDictionary*)packageParams { NSString
*sign; NSMutableString
*reqPars=[NSMutableString string]; //生成簽名 sign
= [self createMd5Sign:packageParams]; //生成xml的package NSArray
*keys = [packageParams allKeys]; [reqPars
appendString:@<xml> ]; for
(NSString *categoryId in keys) { [reqPars
appendFormat:@<%@>%@<!--%@--> ,
categoryId, [packageParams objectForKey:categoryId],categoryId]; } [reqPars
appendFormat:@<sign>%@</sign> </xml>,
sign]; return
[NSString stringWithString:reqPars]; } //提交預支付 -(NSString
*)sendPrepay:(NSMutableDictionary *)prePayParams { NSString
*prepayid = nil; //獲取提交支付 NSString
*send = [self genPackage:prePayParams]; //輸出Debug
Info [self.debugInfo
appendFormat: @API 鏈接:%@ ,
self.payUrl]; [self.debugInfo
appendFormat:@發送的xml:%@ ,
send]; //發送請求post
xml數據 NSData
*res = [WXUtil httpSend:self.payUrl method: @POST
data:send]; //輸出Debug
Info [self.debugInfo
appendFormat:@服務器返回: %@ ,[[NSString
alloc] initWithData:res encoding:NSUTF8StringEncoding]]; XMLHelper
*xml = [[XMLHelper alloc] autorelease]; //開始解析 [xml
startParse:res]; NSMutableDictionary
*resParams = [xml getDict]; //判斷返回 NSString
*return_code = [resParams objectForKey: @return_code ]; NSString
*result_code = [resParams objectForKey: @result_code ]; if
( [return_code isEqualToString: @SUCCESS ]
) { //生成返回數據的簽名 NSString
*sign = [self createMd5Sign:resParams ]; NSString
*send_sign =[resParams objectForKey: @sign ]
; //驗證簽名正確性 if (
[sign isEqualToString:send_sign]){ if (
[result_code isEqualToString: @SUCCESS ])
{ //驗證業務處理狀態 prepayid
= [resParams objectForKey: @prepay_id ]; return_code
= 0 ; [self.debugInfo
appendFormat:@獲取預支付交易標示成功! ]; } } else { self.lastErrCode
= 1 ; [self.debugInfo
appendFormat: @gen_sign =%@ _sign=%@ ,sign,send_sign]; [self.debugInfo
appendFormat:@服務器返回簽名驗證錯誤!!! ]; } } else { self.lastErrCode
= 2 ; [self.debugInfo
appendFormat:@接口返回錯誤!!! ]; } return
prepayid; } -
(NSMutableDictionary*)getPrepayWithOrderName:(NSString*)name price:(NSString*)price device:(NSString*)device { //訂單標題,展示給用戶 NSString*
orderName = name; //訂單金額,單位(分) NSString*
orderPrice = price; //以分爲單位的整數 //支付設備號或門店號 NSString*
orderDevice = device; //支付類型,固定爲APP NSString*
orderType = @APP ; //發器支付的機器ip,暫時沒有發現其作用 NSString*
orderIP = @196 .168. 1.1 ; //隨機數串 srand(
(unsigned)time( 0 )
); NSString
*noncestr = [NSString stringWithFormat:@%d, rand()]; NSString
*orderNO = [NSString stringWithFormat:@%ld,time( 0 )]; //================================ //預付單參數訂單設置 //================================ NSMutableDictionary
*packageParams = [NSMutableDictionary dictionary]; [packageParams
setObject: self.appId forKey: @appid ];
//開放平臺appid [packageParams
setObject: self.mchId forKey: @mch_id ];
//商戶號 [packageParams
setObject: orderDevice forKey: @device_info ];
//支付設備號或門店號 [packageParams
setObject: noncestr forKey: @nonce_str ];
//隨機串 [packageParams
setObject: orderType forKey: @trade_type ];
//支付類型,固定爲APP [packageParams
setObject: orderName forKey: @body ];
//訂單描述,展示給用戶 [packageParams
setObject: NOTIFY_URL forKey: @notify_url ];
//支付結果異步通知 [packageParams
setObject: orderNO forKey: @out_trade_no ]; //商戶訂單號 [packageParams
setObject: orderIP forKey: @spbill_create_ip ]; //發器支付的機器ip [packageParams
setObject: orderPrice forKey: @total_fee ];
//訂單金額,單位爲分 //獲取prepayId(預支付交易會話標識) NSString
*prePayid; prePayid
= [self sendPrepay:packageParams]; if (prePayid
== nil) { [self.debugInfo
appendFormat:@獲取prepayid失敗! ]; return
nil; } //獲取到prepayid後進行第二次簽名 NSString
* package ,
*time_stamp, *nonce_str; //設置支付參數 time_t
now; time(&now); time_stamp
= [NSString stringWithFormat:@%ld, now]; nonce_str
= [WXUtil md5:time_stamp]; //重新按提交格式組包,微信客戶端暫只支持package=Sign=WXPay格式,須考慮升級後支持攜帶package具體參數的情況 //package
= [NSString stringWithFormat:@Sign=%@,package]; package
= @Sign =WXPay; //第二次簽名參數列表 NSMutableDictionary
*signParams = [NSMutableDictionary dictionary]; [signParams
setObject: self.appId forKey: @appid ]; [signParams
setObject: self.mchId forKey: @partnerid ]; [signParams
setObject: nonce_str forKey: @noncestr ]; [signParams
setObject: package
forKey: @package ]; [signParams
setObject: time_stamp forKey: @timestamp ]; [signParams
setObject: prePayid forKey: @prepayid ]; //生成簽名 NSString
*sign = [self createMd5Sign:signParams]; //添加簽名 [signParams
setObject: sign forKey: @sign ]; [self.debugInfo
appendFormat:@第二步簽名成功,sign=%@ ,sign]; //返回參數列表 return
signParams; } @end </code> |
然後,在需要調用微信支付的Controller裏面,新建一個方法。在合適的地方調用。這個方法裏面利用WechatPayManager這個類進行了初始化和參數封裝,然後把上述的核心代碼(PayReq那一段)
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
|
<code
class = "hljs"
objectivec= "" >-
( void )wxPayWithOrderName:(NSString*)name
price:(NSString*)price { //創建支付簽名對象
&& 初始化支付簽名對象 WechatPayManager*
wxpayManager = [[[WechatPayManager alloc]initWithAppID:APP_ID mchID:MCH_ID spKey:PARTNER_ID] autorelease]; //獲取到實際調起微信支付的參數後,在app端調起支付 //生成預支付訂單,實際上就是把關鍵參數進行第一次加密。 NSString*
device = [[UserManager defaultManager]userId]; NSMutableDictionary
*dict = [wxpayManager getPrepayWithOrderName:name price:price device:device]; if (dict
== nil){ //錯誤提示 NSString
*debug = [wxpayManager getDebugInfo]; return ; } NSMutableString
*stamp = [dict objectForKey: @timestamp ]; //調起微信支付 PayReq*
req = [[[PayReq alloc] init]autorelease]; req.openID
= [dict objectForKey: @appid ]; req.partnerId
= [dict objectForKey: @partnerid ]; req.prepayId
= [dict objectForKey: @prepayid ]; req.nonceStr
= [dict objectForKey: @noncestr ]; req.timeStamp
= stamp.intValue; req. package
= [dict objectForKey: @package ]; req.sign
= [dict objectForKey: @sign ]; //
BOOL flag = [WXApi sendReq:req]; BOOL
flag = [WXApi safeSendReq:req]; }</code> |
再者,支付完成了需要調用一個delegate,這個delegate方便個性化顯示支付結果。一般直接把這兩個delegate放在AppDelegate就好了。因爲有一些其他內容也是需要在AppDelegate裏面實現,省的分開找不到。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
<code
class = "hljs"
objectivec= "" >-( void )
onResp:(BaseResp*)resp {
//啓動微信支付的response NSString
*strMsg = [NSString stringWithFormat: @errcode :%d,
resp.errCode]; if ([resp
isKindOfClass:[PayResp class ]]){ //支付返回結果,實際支付結果需要去微信服務器端查詢 switch
(resp.errCode) { case
0 : strMsg
= @支付結果:成功!; break ; case
- 1 : strMsg
= @支付結果:失敗!; break ; case
- 2 : strMsg
= @用戶已經退出支付!; break ; default : strMsg
= [NSString stringWithFormat:@支付結果:失敗!retcode = %d, retstr = %@, resp.errCode,resp.errStr]; break ; } } }</code> |
注意事項:
1)如果APP裏面已經使用了ShareSDK,就有一些地方要注意。不要再重複導入微信的SDK,因爲shareSDK裏面的extend已經包括了微信的SDK。
2)微信本身是鼓勵客戶APP把簽名算法放到服務器上面,這樣信息就不容易被破解。但是如果客戶APP本身沒有服務器端,或者認爲不需要放到服務器端,也可以直接把簽名(加密)的部分直接放在APP端。Sample代碼的註釋有點亂,多次提到服務器云云,但是其實可以不這麼做。
3)微信的price單位是分。注意下即可。
4)暫時想不到,以後想到了再記錄。。