真·iOS内购的完整流程

iOS的内购流程如下

  1. 通过产品ID获取产品信息列表
  2. 添加监听
  3. 把产品包装成SKPayment(支付)发送给苹果服务器
  4. 苹果服务器购买成功后会回调监听方法,根据苹果服务器返回信息判断是否购买成功。
  5. 购买失败或已经购买过该商品则注销交易。如果购买成功,此时可以向自家服务器发送购买成功的消息,并通过后台向苹果服务器发送验证,然后注销交易。
一般而言,这就是iOS内购的基本过程,看似很简单,但是其实实际操作起来,还是比较麻烦的,因为要考虑到各种意外情况。

下面讲一讲iOS内购的具体过程

1.获取产品信息列表
if ([SKPaymentQueue canMakePayments]) {
    NSSet *IDSet = [NSSet setWithArray:proID];
    SKProductsRequest *productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:IDSet];
    productsRequest.delegate = self;
    [productsRequest start];
} else {
    NSLog(@"用户禁止付费");
}

上面代码中的proID就是装有你在开发者后台创建内购产品时输入的产品ID的NSArray。
delegate是指SKProductsRequestDelegate
首先判断用户是否禁止付费,如果没有禁止付费,就想苹果服务器请求产品信息。
请求的信息会在SKProductsRequestDelegate的方法中返回

- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
{
    NSLog(@"%i", response.products.count);
    NSArray *myProducts = response.products;
    if (0 == myProducts.count) {
        NSLog(@"无法获取产品信息列表");
    } else {
        self.products = [myProducts sortedArrayUsingComparator:^NSComparisonResult(id  _Nonnull obj1, id  _Nonnull obj2) {
            SKProduct *pro1 = (SKProduct *)obj1;
            SKProduct *pro2 = (SKProduct *)obj2;
            return pro1.price.integerValue < pro2.price.integerValue ? NSOrderedAscending : NSOrderedDescending;
        }];;
        for (SKProduct *pro in myProducts) {
            NSLog(@"%@", [pro localizedTitle]);
            NSLog(@"%@", [pro localizedDescription]);
            NSLog(@"%@", [pro price]);
            NSLog(@"%@", [pro.priceLocale objectForKey:NSLocaleCurrencySymbol]);
            NSLog(@"%@", [pro.priceLocale objectForKey:NSLocaleCurrencyCode]);
            NSLog(@"%@", [pro productIdentifier]);
        }
    }
}

拿到产品信息以后可以进行排序处理,因为请求的时候发送的产品ID是装在一个NSSet中的,所以返回的产品信息也是乱序的,这里需要注意一下。

2.内购监听

拿到产品信息以后要设置监听,因为当你点击购买和购买后苹果服务器会通过监听方法通知应用。

- (void)startObserver {
    if (!self.isObserver) {
        [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
        NSLog(@"开始监听 ------ 内购");
        self.isObserver = YES;
    }
}

- (void)stopObserver {
    if (self.isObserver) {
        [[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
        NSLog(@"移除监听 ------ 内购");
        self.isObserver = NO;
    }
}

监听方法和移除监听的方法一起送上,isObserver是一个判断是否已经监听的BOOL数据。
我的建议是将监听方法和移除监听的方法都在AppDelegate中执行。当App启动时(- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions)开始监听,当App被关闭时(- (void)applicationWillTerminate:(UIApplication *)application)移除监听。至于原因,后面会提到。

3.实现监听方法

- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions
{
    NSLog(@"调用了几次这个方法?");
    SKPaymentTransaction *transaction = transactions.lastObject;
    switch (transaction.transactionState) {
        case SKPaymentTransactionStatePurchased: {
            NSLog(@"购买完成,向自己的服务器验证 ---- %@", transaction.payment.applicationUsername);
            NSData *data = [NSData dataWithContentsOfFile:[[[NSBundle mainBundle] appStoreReceiptURL] path]];
            NSString *receipt = [data base64EncodedStringWithOptions:0];
            [self buySuccessWithReceipt:receipt transaction:transaction];
        }
            break;
        case SKPaymentTransactionStateFailed: {
            NSLog(@"交易失败");
            [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
        }
            break;
        case SKPaymentTransactionStateRestored: {
            NSLog(@"已经购买过该商品");
            [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
        }
            break;
        case SKPaymentTransactionStatePurchasing: {
            NSLog(@"商品添加进列表");
        }
            break;
        default: {
            NSLog(@"这是什么情况啊?");
        }
            break;
    }
}

finishTransaction:就是注销方法,如果不注销会出现报错和苹果服务器不停的通知监听方法等等情况。总之,记住要注销交易。
有的同学可能会疑惑,transaction.payment.applicationUsername中的这个applicationUsername属性是干嘛的,先不要急,关于这个属性我们会在后面提到,现在先记住这个点就好。

NSData *data = [NSData dataWithContentsOfFile:[[[NSBundle mainBundle] appStoreReceiptURL] path]]; 
NSString *receipt = [data base64EncodedStringWithOptions:0];
[self buySuccessWithReceipt:receipt transaction:transaction];

关于这三句,要注意,receipt是刚才交易的清单,如果后台需要进行二次验证,需要用到这个数据。
至于最后一句,则是购买成功后向自家的服务器发送的请求。

发送内购请求

完成上面的代码后,就可以进行发送内购请求的部分啦

SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:product];
payment.applicationUsername = [AppManager sharedInstance].userId.stringValue;
[[SKPaymentQueue defaultQueue] addPayment:payment];

内购请求很简单,就是用请求道的产品信息SKProduct创建一个“内购支付”SKPayment,然后添加进支付队列。

以上就是iOS内购的全部核心代码。接下来要讲的,就是一些内购中可能出现的坑和如何跳过这些坑。

细心的同学应该已经发现,在发送内购请求的代码部分,我们又一次见到了applicationUsername这个属性,并将用户的id赋值给了它,那么,这是用来干什么的呢?
答案是:在某些极端情况下,可能出现在发送内购请求的用户和内购成功后通知自家后台的用户可能不是同一个用户的情况(真是奇葩的用户。。。。但是没办法,用户就是上帝嘛。。。)这种情况下,为payment绑定一个appUsername就可以让这次payment有一个固定的发起者,这样当这次payment在苹果后台支付成功后,我们就可以通过监听的回调,将这个发起者的唯一标识符上传给自家后台,使得这次购买能找到一个合适的主人。就算用户在购买的过程中切换账号或者退出,也能够让这次充值验证成功。

既然说到了极端情况,那么我们不如更进一步,让情况更极端一点,那就是当购买请求发送后,直到向苹果后台购买成功的这段时间,如果程序崩溃了!或者程序被用户关闭了!怎么办?!
这种情况下,我们的App自然也就无法进行监听的回调,自然也就无法把购买成功的消息发送给自家后台,用户也就拿不到自己的充值啦。情况很糟糕,但是!不用担心,我们有办法解决。
还记得上边提到的注销交易的方法吗?没错就是:

[[SKPaymentQueue defaultQueue] finishTransaction:transaction]

当购买在苹果后台支付成功时,如果你的App没有调用这个方法,那么苹果就不会认为这次交易彻底成功,当你的App再次启动,并且设置了内购的监听时,监听方法- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions就会被调用,直到你调用了上面的方法,注销了这次交易,苹果才会认为这次交易彻底完成。
利用这个特性,我们可以将完成购买后注销方法放到我们向自家后台发送交易成功后调用。
讲到这里,关于内购的大坑我目前遇到的都已经解决啦,当然,你如果实际去操作,可能还会遇到各种各样的小坑,但是没关系,我相信你能够自己解决。。。所以,我就不说啦。
准备爬坑吧!少年!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 161,326评论 4 369
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,228评论 1 304
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 110,979评论 0 252
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,489评论 0 217
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,894评论 3 294
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,900评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,075评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,803评论 0 205
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,565评论 1 249
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,778评论 2 253
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,255评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,582评论 3 261
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,254评论 3 241
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,151评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,952评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,035评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,839评论 2 277

推荐阅读更多精彩内容

  • iOS应用如果涉及到支付功能,分为两类:第三方支付和苹果内购。那么什么情况下选择使用第三方支付,又在什么情况下选择...
    ZfRee阅读 38,338评论 36 66
  • iOS内购流程: iOS内购 什么时候用到呢? 虚拟产品就需要用到iOS内购;购买的商品,是在本app中...
    会跳舞的狮子阅读 2,644评论 5 24
  • 这段时间除了项目的开发,也一直在看内购、Apple Pay、微信支付和支付宝支付的相关官方文档。刚好把需要的文档都...
    李周阅读 22,809评论 16 27
  • 若在人生的舞台上演一场风花雪月,谁将扮演你的对手戏,直至落幕? 有些人问我:“你想找怎么样的另一半?”、“你会爱上...
    黑色指纹阅读 240评论 0 1
  • 动画的本质- 定时器 改变元素的属性- 浏览器/GPU 的渲染过程####动画种类- JavaScript动画 -...
    SeanKChan阅读 575评论 0 0