ios微信支付(ios自學筆記)

對於一個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)暫時想不到,以後想到了再記錄。。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章