iOS接支付寶SDK及遇到的問(wèn)題

接到新需求接入支付寶SDK后先到官方網(wǎng)站上面讀接入文檔支付寶官方文檔。若手機(jī)安裝支付寶則調(diào)起支付寶,沒(méi)有安裝就跳到網(wǎng)頁(yè)支付。

詳細(xì)步驟這里有公司后臺(tái)妹子畫的時(shí)序圖如下:

1.將從支付寶官方下載的SDK拖入到工程中,AlipaySDK.bundle,AlipaySDK.framework,在Build Phases選項(xiàng)卡的Link Binary With Libraries中,增加以下依賴。
image

2.從從后臺(tái)請(qǐng)求接口,得到加密后的字符串,調(diào)起支付寶。此時(shí)需要將處理回調(diào)結(jié)果的方式保存起來(lái),存在delegate中,在App被殺死情況下,通過(guò)在delegate中的方法獲取支付結(jié)果。

-(void)processOrderWithPaymentResult:(NSURL*)resultUrl standbyCallback:(CompletionBlock)completionBlock;
  • 組裝請(qǐng)求信息放在服務(wù)端完成,客戶端要從服務(wù)端拿到請(qǐng)求得到的加密后的字符串信息后,調(diào)起支付寶SDK。
  • 最新版的SDK是智能判斷用戶手機(jī)上是否安裝支付寶錢包APP,如果安裝了就會(huì)調(diào)起支付寶APP進(jìn)行支付,如果沒(méi)有安裝就跳到網(wǎng)頁(yè)支付。
  • 用戶在支付寶錢包之后支付完成后,需要回到調(diào)起的APP,需要在info.plist文件里面設(shè)置自己的url scheme已讓支付寶錢包識(shí)別。
  • 在openUrl方法里面的解析支付寶結(jié)果的函數(shù)只是在APP被殺的情況下獲取支付結(jié)果,正常情況的支付結(jié)果,不管是支付寶錢包支付還是網(wǎng)頁(yè)支付的結(jié)果都會(huì)在調(diào)起支付寶的SDK里面獲取。
  • 在成功調(diào)起支付寶錢包之后的的同步返回結(jié)果中含有code碼如9000,這些錯(cuò)誤碼,不會(huì)作為客戶端判斷支付結(jié)果的依據(jù),最終的依據(jù)還要根據(jù)解析結(jié)果里面的流水號(hào)去調(diào)后端接口進(jìn)行查詢,看付款是否已經(jīng)到賬而作為結(jié)果依據(jù)。
  • 沙箱環(huán)境只支持Android環(huán)境并不支持iOS,要想模擬iOS環(huán)境可以將從后臺(tái)請(qǐng)求到的串放到官方demo里面,看能否調(diào)起SDK,可以將支付金額寫為0.01元(我們后臺(tái)妹子測(cè)試環(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&timestamp=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 = @{};
    //商城大訂單號(hào)
    NSString *orderBigId = kCheckNil(jsDict[@"orderBigId"]);
    //商城支付流水號(hào)
    NSString *orderBizCode = kCheckNil(jsDict[@"orderBizCode"]);
    //支付渠道
    NSString *payWay = kCheckNil(jsDict[@"payWay"]);
    //到后臺(tái)去請(qǐng)求調(diào)起支付寶的加密過(guò)后的串
    [[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èn)題:支付寶錢包支付成功之后跳到支付寶網(wǎng)頁(yè)登錄再次進(jìn)行支付。

現(xiàn)象在iOS10的系統(tǒng)上面我的一切正常,在iOS10以下我在成功調(diào)起支付寶錢包付款之后,回到我的APP又會(huì)進(jìn)入網(wǎng)頁(yè)支付。這就尷尬了,相當(dāng)于提示用戶付款兩次。
經(jīng)過(guò)百度之后,說(shuō)是導(dǎo)入了ShareSDK的問(wèn)題,但是我的APP沒(méi)有導(dǎo)入ShareSDK,用的是友盟,于是我就把友盟刪掉了,但是還是有這個(gè)現(xiàn)象。



剛好碰上周末,支付寶技術(shù)客服不上班(鏈接在這兒輸入簽約的支付寶賬號(hào)或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í)被替換掉了,通過(guò)讀蘋果的官方文檔,對(duì)我的bug現(xiàn)象進(jìn)一步了解了。openurl這個(gè)函數(shù)是有返回值的,如果打開了別的APP就會(huì)返回YES,沒(méi)打開就返回NO,而這兒通過(guò)runtime替換方法之后,直接沒(méi)有返回值了,那就默認(rèn)是返回NO了,那么支付寶SDK內(nèi)部就會(huì)認(rèn)為沒(méi)有打開支付寶錢包,所以又會(huì)跳到網(wǎng)頁(yè)里面去再次進(jìn)行支付,而iOS10則不會(huì)再走這個(gè)方法,而是走的另外一個(gè)openURL:options:completeHander:方法,這個(gè)方法沒(méi)有返回值。不會(huì)造成影響。
所以將上面的方法改為如下就好了。

#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無(wú)法判斷支付結(jié)果。解決辦法詳情請(qǐng)見文檔

上面大致意思是說(shuō):iOS9.0以后,系統(tǒng)左上角多了一個(gè)自帶的返回按鈕。此時(shí),支付寶和微信客戶端都沒(méi)有給回調(diào)到App,需要另外做處理。
image
image
image

1.點(diǎn)擊返回的時(shí)候,支付寶和微信都會(huì)走- (void)applicationWillEnterForeground:(UIApplication *)application方法,此時(shí)告知調(diào)起App的方法此次支付失敗,但是僅僅這樣做事不夠的,因?yàn)閍pp壓后臺(tái),再次打開的情況很多,比如分享返回也走這個(gè)接口,我怎么會(huì)知道是不是支付調(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 = @{};
    //商城大訂單號(hào)
    NSString *orderBigId = kCheckNil(jsDict[@"orderBigId"]);
    //商城支付流水號(hào)
    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返回不僅會(huì)走WillEnterForeground方法,還會(huì)走- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options方法,openUrl的方法是后進(jìn)入的。此時(shí)會(huì)有兩次支付結(jié)果返回。為了解決以上問(wèn)題,用了GCD,在進(jìn)入WillEnterForeground方法里的時(shí)候,讓里面的方法等0.5秒執(zhí)行,如果是有回調(diào)的返回,就利用bool值,變?yōu)閠ure,這是下面判斷這個(gè)bool是ture,WillEnterForeground方法里的判斷就不進(jìn),如果是沒(méi)有回調(diào)的返回,這個(gè)bool值是不會(huì)改變的,0.5秒后繼續(xù)執(zhí)行WillEnterForeground方法里的判斷。

- (void)applicationWillEnterForeground:(UIApplication *)application {
    
    //iOS9.0后點(diǎn)擊左上角,返回會(huì)走此方法,將是點(diǎn)擊左上角返回按鈕返回APP標(biāo)記出來(lái)
    _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),則不會(huì)走下面的回調(diào)而會(huì)走有回調(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),且新浪平臺(tái)僅支持以上回調(diào)。還有以下兩種回調(diào)方式,如果開發(fā)者選取以下回調(diào),也請(qǐng)補(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{

    //針對(duì)iOS9.0以上系統(tǒng),標(biāo)識(shí)此時(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;
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 沒(méi)想到,支付寶的SDK是我目前用過(guò)的所有第三方SDK中最難用的一個(gè)了。 下載 首先,你要想找到這個(gè)SDK,都得費(fèi)點(diǎn)...
    胖花花閱讀 95,476評(píng)論 60 240
  • 本文只討論你已經(jīng)成功實(shí)現(xiàn)客戶端->支付寶支付流程,僅僅是支付完成后無(wú)法返回自己的APP的問(wèn)題。 如果你的支付流程是...
    魔力雙魚閱讀 14,657評(píng)論 3 12
  • iOS支付 iOS支付分為兩類,第三方支付和應(yīng)用內(nèi)支付(內(nèi)購(gòu))。 第三方支付包括:支付寶支付、微信支付、銀聯(lián)支付、...
    請(qǐng)輸入賬號(hào)名閱讀 6,253評(píng)論 3 22
  • ps:先來(lái)支付寶的。首先集成支付寶的SDK,這個(gè)真心是運(yùn)氣加上技術(shù)相結(jié)合才行~有時(shí)候一樣的工程,就是給你報(bào)錯(cuò)~你能...
    臨淵還在閱讀 1,488評(píng)論 0 2
  • 準(zhǔn)備工作: 需要公司的營(yíng)業(yè)執(zhí)照,稅務(wù)信息,等老板的身份證信息等,我記得,用這些材料,去支付寶注冊(cè)一個(gè)商家賬戶(審核...
    Hevin_Chen閱讀 6,821評(píng)論 0 9