iOS接支付宝SDK及遇到的问题

接到新需求接入支付宝SDK后先到官方网站上面读接入文档支付宝官方文档。若手机安装支付宝则调起支付宝,没有安装就跳到网页支付。

详细步骤这里有公司后台妹子画的时序图如下:

1.将从支付宝官方下载的SDK拖入到工程中,AlipaySDK.bundle,AlipaySDK.framework,在Build Phases选项卡的Link Binary With Libraries中,增加以下依赖。
image

2.从从后台请求接口,得到加密后的字符串,调起支付宝。此时需要将处理回调结果的方式保存起来,存在delegate中,在App被杀死情况下,通过在delegate中的方法获取支付结果。

-(void)processOrderWithPaymentResult:(NSURL*)resultUrl standbyCallback:(CompletionBlock)completionBlock;
  • 组装请求信息放在服务端完成,客户端要从服务端拿到请求得到的加密后的字符串信息后,调起支付宝SDK。
  • 最新版的SDK是智能判断用户手机上是否安装支付宝钱包APP,如果安装了就会调起支付宝APP进行支付,如果没有安装就跳到网页支付。
  • 用户在支付宝钱包之后支付完成后,需要回到调起的APP,需要在info.plist文件里面设置自己的url scheme已让支付宝钱包识别。
  • 在openUrl方法里面的解析支付宝结果的函数只是在APP被杀的情况下获取支付结果,正常情况的支付结果,不管是支付宝钱包支付还是网页支付的结果都会在调起支付宝的SDK里面获取。
  • 在成功调起支付宝钱包之后的的同步返回结果中含有code码如9000,这些错误码,不会作为客户端判断支付结果的依据,最终的依据还要根据解析结果里面的流水号去调后端接口进行查询,看付款是否已经到账而作为结果依据。
  • 沙箱环境只支持Android环境并不支持iOS,要想模拟iOS环境可以将从后台请求到的串放到官方demo里面,看能否调起SDK,可以将支付金额写为0.01元(我们后台妹子测试环境这么写的)。
    如下:
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);
        }];

调起支付宝代码如下:

 AppDelegate *delegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
 //此处的回调用于APP被杀情况下,APPDelegate里面openurl方法获取支付结果之后的回调
    delegate.AlipayResultCallback = handler;
    
    NSDictionary *dict = @{};
    //商城大订单号
    NSString *orderBigId = kCheckNil(jsDict[@"orderBigId"]);
    //商城支付流水号
    NSString *orderBizCode = kCheckNil(jsDict[@"orderBizCode"]);
    //支付渠道
    NSString *payWay = kCheckNil(jsDict[@"payWay"]);
    //到后台去请求调起支付宝的加密过后的串
    [[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被杀情况下的支付结果。

        // 其他如支付等SDK的回调
        __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);
        }];

此时有两个方法里面都要去写:

// 支持iOS9以下系统
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation;
// 支持所有iOS系统
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options;

至此客户端的代码就已经写完了。

遇到的问题:支付宝钱包支付成功之后跳到支付宝网页登录再次进行支付。

现象在iOS10的系统上面我的一切正常,在iOS10以下我在成功调起支付宝钱包付款之后,回到我的APP又会进入网页支付。这就尴尬了,相当于提示用户付款两次。
经过百度之后,说是导入了ShareSDK的问题,但是我的APP没有导入ShareSDK,用的是友盟,于是我就把友盟删掉了,但是还是有这个现象。



刚好碰上周末,支付宝技术客服不上班(链接在这儿输入签约的支付宝账号或PartnerID就可以进行在线咨询)。纠结了两天之后,周一支付宝技术客服回答了我。

于是我开始全局搜索我的项目里面的openUrl函数。找到了UIApplication的一个类别(也有叫分类)。

+(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函数确实被替换掉了,通过读苹果的官方文档,对我的bug现象进一步了解了。openurl这个函数是有返回值的,如果打开了别的APP就会返回YES,没打开就返回NO,而这儿通过runtime替换方法之后,直接没有返回值了,那就默认是返回NO了,那么支付宝SDK内部就会认为没有打开支付宝钱包,所以又会跳到网页里面去再次进行支付,而iOS10则不会再走这个方法,而是走的另外一个openURL:options:completeHander:方法,这个方法没有返回值。不会造成影响。
所以将上面的方法改为如下就好了。

#pragma mark - openurl
- (BOOL)swiz_openURL:(NSURL *)url
{
    [self trackUrl:url];
    return [self swiz_openURL:url];
}

此时有一种特殊情况:iOS系统9.0以后,左上角多了一个返回键。在app里调起支付,跳转到支付宝或者微信的时候,左上角有一个返回键,点击这个返回键,支付宝和微信是不给app回调的,因此用户返回app的时候,app无法判断支付结果。解决办法详情请见文档

上面大致意思是说:iOS9.0以后,系统左上角多了一个自带的返回按钮。此时,支付宝和微信客户端都没有给回调到App,需要另外做处理。
image
image
image

1.点击返回的时候,支付宝和微信都会走- (void)applicationWillEnterForeground:(UIApplication *)application方法,此时告知调起App的方法此次支付失败,但是仅仅这样做事不够的,因为app压后台,再次打开的情况很多,比如分享返回也走这个接口,我怎么会知道是不是支付调起的返回呢,那么我就在发起支付的时候,做了一个标记,这里我用了系统单例NSUserDefaults,这样我在返回app的时候,就知道是不是支付返回的了。//此时做个标记:标记是支付调起的支付宝或微信客户端

            [[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)
            {
                //此时做个标记:标记是支付调起的支付宝或微信客户端
                [[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"])
                {
                    //回调字典给H5,成功回调statusCode为9000,失败回调空字符串。
                    [[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.此时若用户点击取消或者完成的时候,app返回不仅会走WillEnterForeground方法,还会走- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options方法,openUrl的方法是后进入的。此时会有两次支付结果返回。为了解决以上问题,用了GCD,在进入WillEnterForeground方法里的时候,让里面的方法等0.5秒执行,如果是有回调的返回,就利用bool值,变为ture,这是下面判断这个bool是ture,WillEnterForeground方法里的判断就不进,如果是没有回调的返回,这个bool值是不会改变的,0.5秒后继续执行WillEnterForeground方法里的判断。

- (void)applicationWillEnterForeground:(UIApplication *)application {
    
    //iOS9.0后点击左上角,返回会走此方法,将是点击左上角返回按钮返回APP标记出来
    _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(), ^{
        //如果回调标记位为YES,表明有回调,则不会走下面的回调而会走有回调的结果
        if (!_weakSelf.isHavedAlipayResultBlock && isActived && [isActived isEqualToString:@"isActived"]) {
            _weakSelf.AlipayResultCallback(NO, @{@"resultStatus":@"",@"errorMsg":@"",@"result":@{}});
        }
    });
    
}
// 支持所有iOS系统
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
{
    return [self handleApplicationOpenURL:url];
}

//注:以上为建议使用的系统openURL回调,且新浪平台仅支持以上回调。还有以下两种回调方式,如果开发者选取以下回调,也请补充相应的函数调用。
- (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系统
- (BOOL)handleApplicationOpenURL:(NSURL *)url{

    //针对iOS9.0以上系统,标识此时有支付宝和微信支付的回调,区别于如果是点击系统左上角返回按钮返回APP还是点击取消和完成返回APP。
    self.isHavedAlipayResultBlock = YES;
    //微信支付
    if([url.scheme isEqualToString:kShareWechatAppkey]){
        return [[PayServer sharedPayServer]handleUrl:url];
    }
    
    BOOL result = [ShareAgent handleOpenURL:url];
    if (!result) {
        // 支付宝SDK的回调
        __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;
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,084评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,623评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,450评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,322评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,370评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,274评论 1 300
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,126评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,980评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,414评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,599评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,773评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,470评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,080评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,713评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,852评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,865评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,689评论 2 354

推荐阅读更多精彩内容