接到新需求接入支付寶SDK后先到官方網(wǎng)站上面讀接入文檔支付寶官方文檔磁滚。若手機(jī)安裝支付寶則調(diào)起支付寶暖侨,沒有安裝就跳到網(wǎng)頁支付状婶。
詳細(xì)步驟這里有公司后臺妹子畫的時(shí)序圖如下:1.將從支付寶官方下載的SDK拖入到工程中你辣,AlipaySDK.bundle,AlipaySDK.framework拦盹,在Build Phases選項(xiàng)卡的Link Binary With Libraries中鹃祖,增加以下依賴。
image
image
2.從從后臺請求接口普舆,得到加密后的字符串恬口,調(diào)起支付寶。此時(shí)需要將處理回調(diào)結(jié)果的方式保存起來沼侣,存在delegate中祖能,在App被殺死情況下,通過在delegate中的方法獲取支付結(jié)果蛾洛。
-(void)processOrderWithPaymentResult:(NSURL*)resultUrl standbyCallback:(CompletionBlock)completionBlock;
- 組裝請求信息放在服務(wù)端完成养铸,客戶端要從服務(wù)端拿到請求得到的加密后的字符串信息后,調(diào)起支付寶SDK轧膘。
- 最新版的SDK是智能判斷用戶手機(jī)上是否安裝支付寶錢包APP钞螟,如果安裝了就會調(diào)起支付寶APP進(jìn)行支付,如果沒有安裝就跳到網(wǎng)頁支付谎碍。
- 用戶在支付寶錢包之后支付完成后鳞滨,需要回到調(diào)起的APP,需要在info.plist文件里面設(shè)置自己的url scheme已讓支付寶錢包識別蟆淀。
- 在openUrl方法里面的解析支付寶結(jié)果的函數(shù)只是在APP被殺的情況下獲取支付結(jié)果拯啦,正常情況的支付結(jié)果,不管是支付寶錢包支付還是網(wǎng)頁支付的結(jié)果都會在調(diào)起支付寶的SDK里面獲取熔任。
- 在成功調(diào)起支付寶錢包之后的的同步返回結(jié)果中含有code碼如9000,這些錯(cuò)誤碼褒链,不會作為客戶端判斷支付結(jié)果的依據(jù),最終的依據(jù)還要根據(jù)解析結(jié)果里面的流水號去調(diào)后端接口進(jìn)行查詢笋敞,看付款是否已經(jīng)到賬而作為結(jié)果依據(jù)碱蒙。
- 沙箱環(huán)境只支持Android環(huán)境并不支持iOS,要想模擬iOS環(huán)境可以將從后臺請求到的串放到官方demo里面,看能否調(diào)起SDK赛惩,可以將支付金額寫為0.01元(我們后臺妹子測試環(huán)境這么寫的)哀墓。
如下:
NSString *orderString = @"alipay_sdk=alipay-sdk-java-dynamicVersionNo&app_id=2017070707675461&biz_content=%7B%22body%22%3A%22%E5%B0%8F%E8%8A%B1%E5%95%86%E5%9F%8E%22%2C%22subject%22%3A%22%E5%B0%8F%E8%8A%B1%E5%95%86%E5%9F%8E%E8%AE%A2%E5%8D%95%22%2C%22out_trade_no%22%3A%2220170821000000005494%22%2C%22timeout_express%22%3A%2230m%22%2C%22total_amount%22%3A%220.01%22%2C%22product_code%22%3A%22QUICK_MSECURITY_PAY%22%2C%22goods_type%22%3A%221%22%2C%22passback_params%22%3A%22orderBizCode%253D20170821000000005494%22%2C%22promo_params%22%3Anull%2C%22extend_params%22%3Anull%2C%22enable_pay_channels%22%3A%22balance%2CcreditCardExpress%2CdebitCardExpress%22%2C%22disable_pay_channels%22%3Anull%2C%22store_id%22%3Anull%7D&charset=utf-8&format=json&method=alipay.trade.app.pay&sign=WmNgLwX5Kyr19zT7CYMZGVDdmZ73nA3w3dOnD2K1GUDRvQZRR8dKzdCqQoZm5ZrdG4kfAzJs9Lcm3fURRKWzKkpLwhMZpFEyhenYg5K5Ainq3021IOvJbmEuWizQoLv4WC6CIyVxz2LnK04iq%2BK5P%2BbC0Nsl12JioJXHY0aGkbtxmCjrILRmL1cqz7BlCO%2Bp1TOq%2BD66H0inoQKJ25r59TMtH9IPakt6Fdsa%2FTZGT8%2BgEX1NoeaLEdzUe7UrxPTqiYEljuxuplcD4jQ1pO5qnUKxYUTPsFZp85tFTGodJNMV38dW6OesJhEWDVMkyK%2Bu2TSwIJu%2B7R27KLR0QBl7JQ%3D%3D&sign_type=RSA2×tamp=2017-08-21+11%3A53%3A28&version=1.0";
[[AlipaySDK defaultService] payOrder:orderString fromScheme:appScheme callback:^(NSDictionary *resultDic) {
NSLog(@"reslut = %@",resultDic);
}];
調(diào)起支付寶代碼如下:
AppDelegate *delegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
//此處的回調(diào)用于APP被殺情況下,APPDelegate里面openurl方法獲取支付結(jié)果之后的回調(diào)
delegate.AlipayResultCallback = handler;
NSDictionary *dict = @{};
//商城大訂單號
NSString *orderBigId = kCheckNil(jsDict[@"orderBigId"]);
//商城支付流水號
NSString *orderBizCode = kCheckNil(jsDict[@"orderBizCode"]);
//支付渠道
NSString *payWay = kCheckNil(jsDict[@"payWay"]);
//到后臺去請求調(diào)起支付寶的加密過后的串
[[ShopManager sharedInstance] alipayOrderBigId:orderBigId inSerialNo:orderBizCode payWay:payWay
onComplete:^(BOOL isSuccessful, id result, NSString *error) {
if (isSuccessful) {
NSDictionary *alipayDict = (NSDictionary *)result;
if (alipayDict) {
if (alipayDict[@"wrappedThreePartyReqBody"])
{
[[AlipaySDK defaultService] payOrder:alipayDict[@"wrappedThreePartyReqBody"] fromScheme:@"xxxxxxxx" callback:^(NSDictionary *resultDic){
NSLog(@"resultDic = %@",resultDic);
handler(YES,resultDic);
}];
}
}
}
else
{
handler(NO,dict);
}
}];
在AppDelegate方法里面openUrl里面獲取APP被殺情況下的支付結(jié)果喷兼。
// 其他如支付等SDK的回調(diào)
__weak typeof(self) _weakSelf = self;
if ([url.host isEqualToString:@"safepay"]) {
[[AlipaySDK defaultService] processOrderWithPaymentResult:url standbyCallback:^(NSDictionary *resultDic) {
XHLog(@"appdegate的方法里面:result = %@",resultDic);
_weakSelf.AlipayResultCallback(YES, resultDic);
}];
此時(shí)有兩個(gè)方法里面都要去寫:
// 支持iOS9以下系統(tǒng)
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation;
// 支持所有iOS系統(tǒng)
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options;
至此客戶端的代碼就已經(jīng)寫完了篮绰。
遇到的問題:支付寶錢包支付成功之后跳到支付寶網(wǎng)頁登錄再次進(jìn)行支付。
現(xiàn)象在iOS10的系統(tǒng)上面我的一切正常季惯,在iOS10以下我在成功調(diào)起支付寶錢包付款之后吠各,回到我的APP又會進(jìn)入網(wǎng)頁支付。這就尷尬了勉抓,相當(dāng)于提示用戶付款兩次贾漏。
經(jīng)過百度之后,說是導(dǎo)入了ShareSDK的問題藕筋,但是我的APP沒有導(dǎo)入ShareSDK纵散,用的是友盟,于是我就把友盟刪掉了隐圾,但是還是有這個(gè)現(xiàn)象伍掀。
剛好碰上周末,支付寶技術(shù)客服不上班(鏈接在這兒輸入簽約的支付寶賬號或PartnerID就可以進(jìn)行在線咨詢)暇藏。糾結(jié)了兩天之后蜜笤,周一支付寶技術(shù)客服回答了我。
于是我開始全局搜索我的項(xiàng)目里面的openUrl函數(shù)盐碱。找到了UIApplication的一個(gè)類別(也有叫分類)把兔。
+(void)load
{
/**
- (void)sendAction:(SEL)action to:(nullable id)target forEvent:(nullable UIEvent *)event;
**/
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
SEL originalSelector = @selector(openURL:);
SEL swizzledSelector = @selector(swiz_openURL:);
[XHSwizUtil swizzlingInClass:[self class] originalSelector:originalSelector swizzledSelector:swizzledSelector];
SEL originalSelector2 = @selector(openURL:options:completionHandler:);
SEL swizzledSelector2 = @selector(swiz_openURL:options:completionHandler:);
[XHSwizUtil swizzlingInClass:[self class] originalSelector:originalSelector2 swizzledSelector:swizzledSelector2];
SEL originalSelector3 = @selector(application:didRegisterForRemoteNotificationsWithDeviceToken:);
SEL swizzledSelector3 = @selector(swiz_application:didRegisterForRemoteNotificationsWithDeviceToken:);
[XHSwizUtil swizzlingInClass:[self class] originalSelector:originalSelector3 swizzledSelector:swizzledSelector3];
});
}
#pragma mark - openurl
- (void)swiz_openURL:(NSURL *)url
{
[self trackUrl:url];
[self swiz_openURL:url];
}
由上面可以看到openurl函數(shù)確實(shí)被替換掉了,通過讀蘋果的官方文檔瓮顽,對我的bug現(xiàn)象進(jìn)一步了解了垛贤。openurl這個(gè)函數(shù)是有返回值的,如果打開了別的APP就會返回YES趣倾,沒打開就返回NO,而這兒通過runtime替換方法之后某饰,直接沒有返回值了儒恋,那就默認(rèn)是返回NO了,那么支付寶SDK內(nèi)部就會認(rèn)為沒有打開支付寶錢包黔漂,所以又會跳到網(wǎng)頁里面去再次進(jìn)行支付诫尽,而iOS10則不會再走這個(gè)方法,而是走的另外一個(gè)openURL:options:completeHander:方法炬守,這個(gè)方法沒有返回值牧嫉。不會造成影響。
所以將上面的方法改為如下就好了。
#pragma mark - openurl
- (BOOL)swiz_openURL:(NSURL *)url
{
[self trackUrl:url];
return [self swiz_openURL:url];
}
此時(shí)有一種特殊情況:iOS系統(tǒng)9.0以后酣藻,左上角多了一個(gè)返回鍵曹洽。在app里調(diào)起支付,跳轉(zhuǎn)到支付寶或者微信的時(shí)候辽剧,左上角有一個(gè)返回鍵送淆,點(diǎn)擊這個(gè)返回鍵,支付寶和微信是不給app回調(diào)的怕轿,因此用戶返回app的時(shí)候偷崩,app無法判斷支付結(jié)果。解決辦法詳情請見文檔撞羽。
上面大致意思是說:iOS9.0以后阐斜,系統(tǒng)左上角多了一個(gè)自帶的返回按鈕。此時(shí)诀紊,支付寶和微信客戶端都沒有給回調(diào)到App,需要另外做處理谒出。
image
image
image
image
image
image
1.點(diǎn)擊返回的時(shí)候,支付寶和微信都會走- (void)applicationWillEnterForeground:(UIApplication *)application方法渡紫,此時(shí)告知調(diào)起App的方法此次支付失敗到推,但是僅僅這樣做事不夠的,因?yàn)閍pp壓后臺惕澎,再次打開的情況很多莉测,比如分享返回也走這個(gè)接口,我怎么會知道是不是支付調(diào)起的返回呢唧喉,那么我就在發(fā)起支付的時(shí)候捣卤,做了一個(gè)標(biāo)記,這里我用了系統(tǒng)單例NSUserDefaults八孝,這樣我在返回app的時(shí)候董朝,就知道是不是支付返回的了。//此時(shí)做個(gè)標(biāo)記:標(biāo)記是支付調(diào)起的支付寶或微信客戶端
[[NSUserDefaults standardUserDefaults] setObject:@"isActived" forKey:@"alipay"];
AppDelegate *delegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
delegate.AlipayResultCallback = handler;
NSDictionary *dict = @{};
//商城大訂單號
NSString *orderBigId = kCheckNil(jsDict[@"orderBigId"]);
//商城支付流水號
NSString *orderBizCode = kCheckNil(jsDict[@"orderBizCode"]);
//支付渠道
NSString *payWay = kCheckNil(jsDict[@"payWay"]);
// orderBigId = 20170927000000005201;
// orderBizCode = 20170927000000000200;
// payWay = alipay;
[[ShopManager sharedInstance] alipayOrderBigId:orderBigId inSerialNo:orderBizCode payWay:payWay
onComplete:^(BOOL isSuccessful, id result, NSString *error) {
if (isSuccessful)
{
NSDictionary *alipayDict = (NSDictionary *)result;
if (alipayDict)
{
//此時(shí)做個(gè)標(biāo)記:標(biāo)記是支付調(diào)起的支付寶或微信客戶端
[[NSUserDefaults standardUserDefaults] setObject:@"isActived" forKey:@"alipay"];
if (alipayDict[@"wrappedThreePartyReqBody"] && [payWay isEqualToString:@"alipay"])
{
[[AlipaySDK defaultService] payOrder:alipayDict[@"wrappedThreePartyReqBody"] fromScheme:@"xhscmall" callback:^(NSDictionary *resultDic){
XHLog(@"resultDic = %@",resultDic);
handler(YES,resultDic);
}];
}
else if (alipayDict[@"wrappedThreePartyReqBody"] && [payWay isEqualToString:@"wechatpay"])
{
//回調(diào)字典給H5,成功回調(diào)statusCode為9000干跛,失敗回調(diào)空字符串子姜。
[[PayServer sharedPayServer] wxPay:alipayDict[@"wrappedThreePartyReqBody"] withcomplete:^(PayType type, NSDictionary * _Nonnull message) {
if (type == PaySuccess) {
handler(YES,message);
}else if(type == PayCancle){
handler(NO,message);
}else if (type == PayFail){
handler(NO,message);
}
}];
}
}
}
else
{
handler(NO,dict);
}
}];
2.此時(shí)若用戶點(diǎn)擊取消或者完成的時(shí)候,app返回不僅會走WillEnterForeground方法楼入,還會走- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options方法哥捕,openUrl的方法是后進(jìn)入的。此時(shí)會有兩次支付結(jié)果返回嘉熊。為了解決以上問題遥赚,用了GCD,在進(jìn)入WillEnterForeground方法里的時(shí)候阐肤,讓里面的方法等0.5秒執(zhí)行凫佛,如果是有回調(diào)的返回讲坎,就利用bool值,變?yōu)閠ure愧薛,這是下面判斷這個(gè)bool是ture晨炕,WillEnterForeground方法里的判斷就不進(jìn),如果是沒有回調(diào)的返回厚满,這個(gè)bool值是不會改變的府瞄,0.5秒后繼續(xù)執(zhí)行WillEnterForeground方法里的判斷。
- (void)applicationWillEnterForeground:(UIApplication *)application {
//iOS9.0后點(diǎn)擊左上角碘箍,返回會走此方法,將是點(diǎn)擊左上角返回按鈕返回APP標(biāo)記出來
_isHavedAlipayResultBlock = NO;
NSString *isActived = [[NSUserDefaults standardUserDefaults] objectForKey:@"alipay"];
__weak typeof(self) _weakSelf = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//如果回調(diào)標(biāo)記位為YES,表明有回調(diào)遵馆,則不會走下面的回調(diào)而會走有回調(diào)的結(jié)果
if (!_weakSelf.isHavedAlipayResultBlock && isActived && [isActived isEqualToString:@"isActived"]) {
_weakSelf.AlipayResultCallback(NO, @{@"resultStatus":@"",@"errorMsg":@"",@"result":@{}});
}
});
}
// 支持所有iOS系統(tǒng)
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
{
return [self handleApplicationOpenURL:url];
}
//注:以上為建議使用的系統(tǒng)openURL回調(diào),且新浪平臺僅支持以上回調(diào)丰榴。還有以下兩種回調(diào)方式货邓,如果開發(fā)者選取以下回調(diào),也請補(bǔ)充相應(yīng)的函數(shù)調(diào)用四濒。
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options
{
return [self handleApplicationOpenURL:url];
}
-(BOOL)application:(UIApplication *)app handleOpenURL:(nonnull NSURL *)url
{
return [self handleApplicationOpenURL:url];
}
//2.支持目前所有iOS系統(tǒng)
- (BOOL)handleApplicationOpenURL:(NSURL *)url{
//針對iOS9.0以上系統(tǒng)换况,標(biāo)識此時(shí)有支付寶和微信支付的回調(diào),區(qū)別于如果是點(diǎn)擊系統(tǒng)左上角返回按鈕返回APP還是點(diǎn)擊取消和完成返回APP盗蟆。
self.isHavedAlipayResultBlock = YES;
//微信支付
if([url.scheme isEqualToString:kShareWechatAppkey]){
return [[PayServer sharedPayServer]handleUrl:url];
}
BOOL result = [ShareAgent handleOpenURL:url];
if (!result) {
// 支付寶SDK的回調(diào)
__weak typeof(self) _weakSelf = self;
if ([url.host isEqualToString:@"safepay"]) {
[[AlipaySDK defaultService] processOrderWithPaymentResult:url standbyCallback:^(NSDictionary *resultDic) {
NSLog(@"appdegate的方法里面:result = %@",resultDic);
_weakSelf.AlipayResultCallback(YES, resultDic);
}];
}
return YES;
}
return result;
}