自己總結(jié)的微信支付寶支付流程和注意點(diǎn):
準(zhǔn)備工作:
需要公司的營(yíng)業(yè)執(zhí)照,稅務(wù)信息,等老板的身份證信息等,我記得,用這些材料,去支付寶注冊(cè)一個(gè)商家賬戶(hù)(審核周期大概5個(gè)工作日),或者微信的開(kāi)發(fā)者賬號(hào)(審核周期大概5個(gè)工作日,300元費(fèi)用),微信的話(huà),需要你的app已經(jīng)上架有了APPID,才能開(kāi)通;大概也是5個(gè)工作日
用微信支付時(shí):用戶(hù)點(diǎn)擊支付按鈕時(shí)要先判斷有沒(méi)有安裝微信,以及當(dāng)前的微信版本是否支持支付,都滿(mǎn)足則進(jìn)入到微信界面,調(diào)起微信支付,在進(jìn)行微信支付的
支付文檔鏈接:https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=8_1
支付寶支付分為:支付寶有“移動(dòng)快捷支付”(支付時(shí)跳轉(zhuǎn)到支付寶APP,需要用戶(hù)安裝支付寶APP),或者“移動(dòng)WAP網(wǎng)頁(yè)支付”(支付時(shí)打開(kāi)一個(gè)WebView里邊登陸支付寶進(jìn)行支付)我看到大部分app美團(tuán)大眾點(diǎn)評(píng)支付寶支付都是第二種
支付寶支付步驟:
1.先與支付寶簽約,獲得商戶(hù)ID(partner)和賬號(hào)ID(seller)
2.下載相應(yīng)的公鑰私鑰文件(用來(lái)加密簽名)
3.下載支付寶SDK
4.生產(chǎn)訂單信息(客戶(hù)端或放在服務(wù)端生成訂單號(hào),支付寶支付成功后會(huì)通知服務(wù)端,這樣在服務(wù)端生成訂單的話(huà),可以掌握所有的訂單,而且還會(huì)更加安全)
5.調(diào)用支付寶支付接口發(fā)送訂單,由支付寶客戶(hù)端跟支付寶安全服務(wù)器打交道
6.支付完畢后返回支付結(jié)果給商戶(hù)客戶(hù)端,進(jìn)行結(jié)構(gòu)處理
安全問(wèn)題:接收到的支付結(jié)果可能被截獲修改。所以,就需要在生成訂單和處理支付結(jié)果時(shí)做安全性校驗(yàn)
生成訂單時(shí)對(duì)數(shù)據(jù)簽名,收到支付結(jié)果時(shí)對(duì)數(shù)據(jù)進(jìn)行簽名驗(yàn)證,以檢驗(yàn)數(shù)據(jù)是否被篡改過(guò)。
支付寶目前只支持采用RSA加密方式做簽名驗(yàn)證。
RSA加密算法除了可加解密外,還可用來(lái)作簽名校驗(yàn)。
簡(jiǎn)單的說(shuō),RSA會(huì)生成一個(gè)私鑰和一個(gè)公鑰,私鑰你應(yīng)該獨(dú)自保管,公鑰你可以分發(fā)出去。
做簽名驗(yàn)證時(shí),你可以用私鑰對(duì)需要傳輸?shù)臄?shù)據(jù)做簽名加密,生成一個(gè)簽名值,之后分發(fā)數(shù)據(jù),接收方通過(guò)公鑰對(duì)簽名值做校驗(yàn),如果一致則認(rèn)為數(shù)據(jù)無(wú)篡改
具體到支付寶使用RSA做簽名驗(yàn)證,就是在生產(chǎn)訂單時(shí),需要使用私鑰生成簽名值;在處理返回的支付結(jié)果時(shí),需要使用公鑰驗(yàn)證返回結(jié)果是否被篡改了。
實(shí)例代碼:
@WeakObj(self);
NDHttpRequestManage*request = [NDHttpRequestManagerequestManage];
//需要傳給支付寶的AppScheme
NSString*appScheme =@"aaa2015080600202133";
[requestuserObtainAliSign:[self.user.userIDndValue]
AndTradeNo:_tradeNo
complete:^(idcompleteObject,NSError*error )
{
@StrongObj(self);
[self.indicationstopAnimationWithLoadText:@"finish"withType:YES];
self.indication=nil;
if(!error){
//簽名信息以及訂單信息
NSString*String = [completeObjectobjectForKey:@"sign"];
NSString*orderSpec = [completeObjectobjectForKey:@"content"];
if([signisKindOfClass:[NSNullclass]] || sign ==nil){
[selfshowDissmissSelfAlertViewTitle:@""message:@"支付失敗,請(qǐng)重新嘗試。"complete:^{
}];
return;}
//urlcode加密
NSString*signedString = [selfurlEncodedString:String];
NSString*orderString ?=nil;
if(signedString !=nil){
//生成訂單信息及簽名請(qǐng)求參數(shù)
orderString = [NSStringstringWithFormat:@"%@&sign=\"%@\"&sign_type=\"%@\"",
orderSpec, signedString,@"RSA"];
}
//調(diào)用支付寶的SDK[[AlipaySDKdefaultService]payOrder:orderStringfromScheme:appSchemecallback:^(NSDictionary*resultDic){
//resultDic ?這個(gè)字典是返回我所有的支付成功或者失敗的信息
NSString* key =@"ResultStatus";
if(![[resultDicallKeys]containsObject:key]) {
key =@"resultStatus";
}
if(key !=nil){
//返回9000 就是成功
if([[resultDicobjectForKey:key]integerValue] ==9000) {
//訂單成功.
[selfpaySuccess];
}else{
//訂單失敗.
[selfpayFailed];
}
}
}];
}
}];
-(NSString*)urlEncodedString:(NSString*)string
{
NSString* encodedString = (__bridge_transferNSString*)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (__bridgeCFStringRef)string,NULL, (__bridgeCFStringRef)@"!*'();:@&=+$,/?%#[]",kCFStringEncodingUTF8);
returnencodedString;
}
集成
清楚了流程后,就好理解怎么集成了。
支付SDK
如果只需要發(fā)送訂單和處理支付返回結(jié)果,只需要添加AlipaySDK.bundle和AlipaySDK.framework就行了。
這里再吐槽下,之前用的舊版本,和現(xiàn)在的版本相比,還不光是把類(lèi)名字給改了,原先是用的類(lèi)方法,現(xiàn)在新版又給改成了單例了。。還真是任性啊,這要是哪家小廠(chǎng)的SDK,估計(jì)早被棄用了把。。
發(fā)送訂單的方法:
- (void)payOrder:(NSString *)orderStr
fromScheme:(NSString *)schemeStr
callback:(CompletionBlock)completionBlock;
如果手機(jī)內(nèi)沒(méi)安裝支付寶的app,會(huì)直接展現(xiàn)支付寶web支付界面,通過(guò)callback返回支付結(jié)果;
如果手機(jī)內(nèi)安裝了支付寶的app,會(huì)跳轉(zhuǎn)到支付寶的app支付,然后通過(guò)openURL的回調(diào)返回支付結(jié)果。
支付寶的SDK只給了一個(gè)處理返回結(jié)果的方法,而不像其他第三方的SDK提供一個(gè)處理openURL的方法,所以你需要通過(guò)DEMO或者在第二個(gè)文檔里找到處理openURL的方式:
if ([url.host isEqualToString:@"safepay"]) {
[[AlipaySDK defaultService] processOrderWithPaymentResult:url
standbyCallback:^(NSDictionary *resultDic) {
NSLog(@"result = %@",resultDic);
}]; }
SDK也提供了一個(gè)處理openURL返回結(jié)果的方法
- (void)processOrderWithPaymentResult:(NSURL *)resultUrl
standbyCallback:(CompletionBlock)completionBlock;
兩個(gè)回調(diào)block都統(tǒng)一定義為typedef void(^CompletionBlock)(NSDictionary *resultDic);,
返回了一個(gè)字典,但是SDK里完全沒(méi)有提示有哪些key。。
你可以在文檔里找到,或者自己實(shí)際試一下,返回的信息如下:
resultStatus,狀態(tài)碼,SDK里沒(méi)對(duì)應(yīng)信息,第一個(gè)文檔里有提到:
9000 訂單支付成功
8000 正在處理中
4000 訂單支付失敗
6001 用戶(hù)中途取消
6002 網(wǎng)絡(luò)連接出錯(cuò)
memo, 提示信息,比如狀態(tài)碼為6001時(shí),memo就是“用戶(hù)中途取消”。但千萬(wàn)別完全依賴(lài)這個(gè)信息,如果未安裝支付寶app,采用網(wǎng)頁(yè)支付時(shí),取消時(shí)狀態(tài)碼是6001,但這個(gè)memo是空的。。(當(dāng)我發(fā)現(xiàn)這個(gè)問(wèn)題的時(shí)候,我就決定,對(duì)于這么不靠譜的SDK,還是盡量靠自己吧。。)
result,訂單信息,以及簽名驗(yàn)證信息。如果你不想做簽名驗(yàn)證,那這個(gè)字段可以忽略了。。
如果你對(duì)支付的安全性不那么在意或重視的話(huà),到這里就可以完成支付寶的集成了。
如果想更加安全,還是需要增加下面的簽名驗(yàn)證的。
簽名驗(yàn)證
首先,RSA只是一種算法,所以你可以使用任何一種開(kāi)源的、或者自己去實(shí)現(xiàn)這個(gè)算法來(lái)實(shí)現(xiàn)簽名和驗(yàn)證的目的。
在整個(gè)流程當(dāng)中,因?yàn)樯婕暗搅薘SA公鑰、私鑰的生產(chǎn),RSA的簽名、驗(yàn)證簽名,SHA1值的計(jì)算,base64和URL編碼,所以支付寶用了一個(gè)開(kāi)源的代碼來(lái)統(tǒng)一解決這些問(wèn)題,就是openssl(順便再吐槽下,這DEMO里一放openssl,不知道又會(huì)引來(lái)多少公司的產(chǎn)品里使用openssl了,估計(jì)阿里自己也沒(méi)少用,什么時(shí)候都能跟老羅、華為一樣去贊助點(diǎn)呢。。)
如果你想省事,也用openssl,那你需要把這些東西都加入到項(xiàng)目中:DEMO中的openssl目錄頭文件,兩個(gè)庫(kù)文件libcrypto.a libssl.a,DEMO里支付寶自己寫(xiě)的Util目錄
訂單簽名
上面說(shuō)了,訂單簽名應(yīng)該用私鑰,但是把私鑰放到app里其實(shí)本身就不安全,因?yàn)槟愕腶pp是分發(fā)到用戶(hù)手里的,私鑰應(yīng)該放在自己的手里,分發(fā)出去的應(yīng)該是公鑰。
所以私鑰最好是放在自己的服務(wù)器上,訂單加密這個(gè)工作放在服務(wù)器端來(lái)做,服務(wù)器將包含簽名的訂單信息返回給app,app再通過(guò)SDK發(fā)送給支付寶,這樣會(huì)更安全些;而且服務(wù)器也能掌握所有的訂單狀況。
如果你非要將私鑰集成到app里,那可以參考SDK的DEMO,因?yàn)檫@個(gè)DEMO就是在app本地通過(guò)私鑰做的訂單簽名。。
支付結(jié)果簽名驗(yàn)證
上面的回調(diào)block提到了返回的內(nèi)容,返回的支付結(jié)果中的result字段里是帶有訂單信息和簽名信息的,所以簽名驗(yàn)證就是需要這個(gè)字段的值。
文檔中有一個(gè)這個(gè)字段的例子,實(shí)際結(jié)果沒(méi)有換行,我換一下行便于閱讀:
partner="2088101568358171"&seller_id="xxx@alipay.com"&out_trade_no="0819145412-6177"&subject="測(cè)試"&body="測(cè)試測(cè)試"&total_fee="0.01"?ify_url="http://notify.msp.hk/notify.htm"&service="mobile.securitypay.pay"&payment_type="1"&_input_charset="utf-8"&it_b_pay="30m"&success="true"&sign_type="RSA"&sign="hkFZr+zE9499nuqDNLZEF7W75RFFPsly876QuRSeN8WMaUgcdR00IKy5ZyBJ4eldhoJ/2zghqrD4E2G2mNjs3aE+HCLiBXrPDNdLKCZ gSOIqmv46TfPTEqopYfhs+o5fZzXxt34fwdrzN4mX6S13cr3UwmEV4L3Ffir/02RBVtU="
總共分為三個(gè)部分
第一部分是訂單信息,每個(gè)字段的具體含義可以在文檔里找;
中間sign_type是簽名用的算法,文檔里說(shuō)了,目前只支持RSA;
最后的sign就是簽名值。
驗(yàn)證的步驟如下:
首先把訂單信息和簽名值分別提取出來(lái)(SDK居然都不給處理好。。)
訂單信息就是sign_type的連字符&之前的所有字符串
簽名值是sign后面雙引號(hào)內(nèi)的內(nèi)容,注意簽名的結(jié)尾也是=,所以不要用split字符串的方式提取
如果你想簡(jiǎn)單,可以直接使用Util目錄下的DataVerifier來(lái)作簽名驗(yàn)證
- (BOOL)verifyString:(NSString *)string withSign:(NSString *)signString;
第一個(gè)參數(shù)就是訂單信息,第二個(gè)參數(shù)就是簽名值。
其實(shí)不使用openssl,用其他第三方RSA的開(kāi)源代碼也是可以的。可以看下DEMO里openssl_wrapper的源碼和SDK的文檔。
對(duì)于訂單信息,先做一個(gè)base64編碼(DEMO中這個(gè)還要調(diào)openssl來(lái)實(shí)現(xiàn)。。),再計(jì)算SHA1的值(這個(gè)也可以完全不用openssl,蘋(píng)果的庫(kù)中都有的。。),然后再簽名比對(duì)。
對(duì)于公鑰,如果使用其他第三方代碼,需要注意格式問(wèn)題。支付寶的DEMO實(shí)現(xiàn)中,是把這個(gè)公鑰又轉(zhuǎn)回成openssl生成的本地文件格式,然后再寫(xiě)入本地文件,再讓openssl讀取出來(lái)使用。。
以上,就是支付寶 iOS SDK的一些介紹。
總體來(lái)說(shuō),我覺(jué)得能靠自己處理的地方還是盡量不要依賴(lài)這個(gè)不太靠譜的SDK了。
微信支付步驟:https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_1#
郵件中參數(shù)API參數(shù)名詳細(xì)說(shuō)明
APPIDappidappid是微信公眾賬號(hào)或開(kāi)放平臺(tái)APP的唯一標(biāo)識(shí),在公眾平臺(tái)申請(qǐng)公眾賬號(hào)或者在開(kāi)放平臺(tái)申請(qǐng)APP賬號(hào)后,微信會(huì)自動(dòng)分配對(duì)應(yīng)的appid,用于標(biāo)識(shí)該應(yīng)用。可在微信公眾平臺(tái)-->開(kāi)發(fā)者中心查看,商戶(hù)的微信支付審核通過(guò)郵件中也會(huì)包含該字段值。
微信支付商戶(hù)號(hào)mch_id商戶(hù)申請(qǐng)微信支付后,由微信支付分配的商戶(hù)收款賬號(hào)。
API密鑰key交易過(guò)程生成簽名的密鑰,僅保留在商戶(hù)系統(tǒng)和微信支付后臺(tái),不會(huì)在網(wǎng)絡(luò)中傳播。商戶(hù)妥善保管該Key,切勿在網(wǎng)絡(luò)中傳輸,不能在其他客戶(hù)端中存儲(chǔ),保證key不會(huì)被泄漏。商戶(hù)可根據(jù)郵件提示登錄微信商戶(hù)平臺(tái)進(jìn)行設(shè)置。也可按一下路徑設(shè)置:微信商戶(hù)平臺(tái)(pay.weixin.qq.com)-->賬戶(hù)設(shè)置-->API安全-->密鑰設(shè)置
AppsecretsecretAppSecret是APPID對(duì)應(yīng)的接口密碼,用于獲取接口調(diào)用憑證access_token時(shí)使用。
商戶(hù)系統(tǒng)和微信支付系統(tǒng)主要交互說(shuō)明:
步驟1:用戶(hù)在商戶(hù)APP中選擇商品,提交訂單,選擇微信支付。
步驟2:商戶(hù)后臺(tái)收到用戶(hù)支付單,調(diào)用微信支付統(tǒng)一下單接口。參見(jiàn)【統(tǒng)一下單API】。
步驟3:統(tǒng)一下單接口返回正常的prepay_id,再按簽名規(guī)范重新生成簽名后,將數(shù)據(jù)傳輸給APP。參與簽名的字段名為appId,partnerId,prepayId,nonceStr,timeStamp,package。注意:package的值格式為Sign=WXPay
以上信息都是有后臺(tái)服務(wù)器調(diào)用微信的支付接口獲得并返給App端的
步驟4:商戶(hù)APP調(diào)起微信支付。api參見(jiàn)本章節(jié)【app端開(kāi)發(fā)步驟說(shuō)明】
步驟5:商戶(hù)后臺(tái)接收支付通知。api參見(jiàn)【支付結(jié)果通知API】
步驟6:商戶(hù)后臺(tái)查詢(xún)支付結(jié)果。,api參見(jiàn)【查詢(xún)訂單API】
例子
NDHttpRequestManage*request = [NDHttpRequestManagerequestManage];
[requestuserGetWeiXinSign:[self.user.userIDndValue]AndTradeNo:_tradeNocomplete:^(idcompleteObject,NSError*error)
{
@StrongObj(self);
[self.indicationstopAnimationWithLoadText:@"finish"withType:YES];
self.indication=nil;
if(!error)
{
appDelegate.loginDone=nil;
NSString*str = completeObject[@"sign"];
[selfcheckSignFromSign:str];
NSData*jsonData = [strdataUsingEncoding:NSUTF8StringEncoding];
NSError*err;
NSDictionary*dict = [NSJSONSerializationJSONObjectWithData:jsonData
options:NSJSONReadingMutableContainers
error:&err];
NSMutableString*prepayId = [dictobjectForKey:@"prepayid"];
if(prepayId){
NSMutableString*stamp ?= [dictobjectForKey:@"timestamp"];
//調(diào)起微信支付
PayReq* req ? ? ? ? ? ? = [[PayReqalloc]init];
req.openID=[dictobjectForKey:@"appid"];
req.partnerId= [dictobjectForKey:@"partnerid"];
req.prepayId= [dictobjectForKey:@"prepayid"];
req.nonceStr= [dictobjectForKey:@"noncestr"];
req.timeStamp= stamp.intValue;
req.package=@"Sign=WXPay";
req.sign= [dictobjectForKey:@"sign"];
BOOLret = ? [WXApisendReq:req];
if(ret ==NO)
{
[selfshowDissmissSelfAlertViewTitle:@""message:@"支付失敗,請(qǐng)重新嘗試。"complete:^{
}];
}
}
}
}];
以下項(xiàng)目開(kāi)發(fā)環(huán)境以Xcode10.0,運(yùn)行環(huán)境為IOS7.0為例,說(shuō)明其開(kāi)發(fā)中需要的操作。
1、項(xiàng)目設(shè)置APPID
商戶(hù)在微信開(kāi)放平臺(tái)申請(qǐng)開(kāi)發(fā)APP應(yīng)用后,微信開(kāi)放平臺(tái)會(huì)生成APP的唯一標(biāo)識(shí)APPID。在Xcode中打開(kāi)項(xiàng)目,設(shè)置項(xiàng)目屬性中的URL Schemes為您的APPID。如圖8.7標(biāo)紅位置所示。
圖8.7
2、注冊(cè)APPID
商戶(hù)APP工程中引入微信lib庫(kù)和頭文件,調(diào)用API前,需要先向微信注冊(cè)您的APPID,代碼如下:
[WXApi registerApp:@"wxd930ea5d5a258f4f" withDescription:@"demo 2.0"];
3、調(diào)起支付
商戶(hù)服務(wù)器生成支付訂單,先調(diào)用【統(tǒng)一下單API】生成預(yù)付單,獲取到prepay_id后將參數(shù)再次簽名傳輸給APP發(fā)起支付。以下是調(diào)起微信支付的關(guān)鍵代碼:
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];
注意:該sign生成字段名列表見(jiàn)調(diào)起支付API
4、支付結(jié)果回調(diào)
照微信SDK Sample,在類(lèi)實(shí)現(xiàn)onResp函數(shù),支付完成后,微信APP會(huì)返回到商戶(hù)APP并回調(diào)onResp函數(shù),開(kāi)發(fā)者需要在該函數(shù)中接收通知,判斷返回錯(cuò)誤碼,如果支付成功則去后臺(tái)查詢(xún)支付結(jié)果再展示用戶(hù)實(shí)際支付結(jié)果。注意 一定不能以客戶(hù)端返回作為用戶(hù)支付的結(jié)果,應(yīng)以服務(wù)器端的接收的支付通知或查詢(xún)API返回的結(jié)果為準(zhǔn)。代碼示例如下:
-(void)onResp:(BaseResp*)resp{
if ([respisKindOfClass:[PayRespclass]]){
PayResp*response=(PayResp*)resp;
switch(response.errCode){
caseWXSuccess:
//服務(wù)器端查詢(xún)支付通知或查詢(xún)API返回的結(jié)果再提示成功
NSlog(@"支付成功");
break;
default:
NSlog(@"支付失敗,retcode=%d",resp.errCode);
break;
}
}
}
回調(diào)中errCode值列表:
名稱(chēng)描述解決方案
0成功展示成功頁(yè)面
-1錯(cuò)誤可能的原因:簽名錯(cuò)誤、未注冊(cè)APPID、項(xiàng)目設(shè)置APPID不正確、注冊(cè)的APPID與設(shè)置的不匹配、其他異常等。
-2用戶(hù)取消無(wú)需處理。發(fā)生場(chǎng)景:用戶(hù)不支付了,點(diǎn)擊取消,返回APP。