對於一個iOS的APP,如果有一些虛擬的商品或者服務需要通過在線支付來收費的話,一般有幾種主流的選擇。
如果是通過APP調用支付平台APP的思路的話,一個是調起支付寶客戶端,一個則是調起微信支付。
實際上,從代碼的角度,調起支付APP就是把一些關鍵的參數通過一定方式打包成為一個訂單,然後發送到支付平台的服務器。所以,只要搞清楚了參數設置,搞清楚了每個支付平台的SDK裡面一些關鍵API的使用,基本上就可以很簡單的支持支付。
今天記錄一下客戶端裡面,如何支持微信支付。首先。我們要仔細閱讀一下微信SDK的開發文檔,了解一下整個支付的大概流程。
然後根據提示,把相應的SDK下載下來,所謂的SDK,也就是一個鏈接庫和兩個頭文件,很簡單。
下載完畢,需要把SDK導入到工程裡面,並且配置一下工程。因為開發者文檔已經有詳細描述,這裡就不再復述。
http://pay.weixin.qq.com/wiki/doc/api/app.php?chapter=8_5
從文檔看到,調起微信支付其實最核心的是一下這麼一段
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];
這裡的范例是一段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不變
//
// WechatPayManager.h
//
// Created by HuangCharlie on 5/24/15.
//
//
#import
#import "WXUtil.h"
#import "ApiXml.h"
#import "WXApi.h"
// 賬號帳戶資料
// 更改商戶把相關參數後可測試
#define APP_ID @"wx427a2f57bc4456d1" //APPID
#define APP_SECRET @"" //appsecret,看起來好像沒用
//商戶號,填寫商戶對應參數
#define MCH_ID @"1242316102"
//商戶API密鑰,填寫相應參數
#define PARTNER_ID @"12345678901234567890123456789020"
//支付結果回調頁面
#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
//
// 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簽名字符串:\n%@\n\n",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:@"\n"];
for (NSString *categoryId in keys) {
[reqPars appendFormat:@"<%@>%@\n", categoryId, [packageParams objectForKey:categoryId],categoryId];
}
[reqPars appendFormat:@"%@ \n ", sign];
return [NSString stringWithString:reqPars];
}
//提交預支付
-(NSString *)sendPrepay:(NSMutableDictionary *)prePayParams
{
NSString *prepayid = nil;
//獲取提交支付
NSString *send = [self genPackage:prePayParams];
//輸出Debug Info
[self.debugInfo appendFormat:@"API鏈接:%@\n", self.payUrl];
[self.debugInfo appendFormat:@"發送的xml:%@\n", send];
//發送請求post xml數據
NSData *res = [WXUtil httpSend:self.payUrl method:@"POST" data:send];
//輸出Debug Info
[self.debugInfo appendFormat:@"服務器返回:\n%@\n\n",[[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:@"獲取預支付交易標示成功!\n"];
}
}else{
self.lastErrCode = 1;
[self.debugInfo appendFormat:@"gen_sign=%@\n _sign=%@\n",sign,send_sign];
[self.debugInfo appendFormat:@"服務器返回簽名驗證錯誤!!!\n"];
}
}else{
self.lastErrCode = 2;
[self.debugInfo appendFormat:@"接口返回錯誤!!!\n"];
}
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失敗!\n"];
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=%@\n",sign];
//返回參數列表
return signParams;
}
@end
然後,在需要調用微信支付的Controller裡面,新建一個方法。在合適的地方調用。這個方法裡面利用WechatPayManager這個類進行了初始化和參數封裝,然後把上述的核心代碼(PayReq那一段)
- (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];
}
再者,支付完成了需要調用一個delegate,這個delegate方便個性化顯示支付結果。一般直接把這兩個delegate放在AppDelegate就好了。因為有一些其他內容也是需要在AppDelegate裡面實現,省的分開找不到。
-(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;
}
}
}
注意事項:
1)如果APP裡面已經使用了ShareSDK,就有一些地方要注意。不要再重復導入微信的SDK,因為shareSDK裡面的extend已經包括了微信的SDK。
2)微信本身是鼓勵客戶APP把簽名算法放到服務器上面,這樣信息就不容易被破解。但是如果客戶APP本身沒有服務器端,或者認為不需要放到服務器端,也可以直接把簽名(加密)的部分直接放在APP端。Sample代碼的注釋有點亂,多次提到服務器雲雲,但是其實可以不這麼做。
3)微信的price單位是分。注意下即可。
4)暫時想不到,以後想到了再記錄。。