SDK接入(3)之iOS内支付(In-App Purchase)接入

SDK接入(3)之iOS内支付(In-App Purchase)接入

继整理了Android平台的SDK接入过程。再来分享下iOS平台的内支付(In-App Purchase)接入,作为笔者在游戏开发中实际遇到的,觉得有必要分享下,同时也当作是对工作的总结,就放在该SDK接入系列文章中了。

作为SDK接入系列,同时也是Android平台的SDK接入有:
SDK接入(2)之Android Google Play内支付(in-app Billing)接入
SDK接入(1)之Android Facebook SDK接入

这里提一点,SDK的接入,官方文档肯定最详细,最准确,而且有时效性,接入流程变化,API修改更新,肯定最终都以官方的为准。那么,苹果官方内支付(IAP)接入文档地址为:

In-App Purchase Programming Guide

iOS内支付流程

1.商品种类

在了解苹果IAP内支付之前,有必要先了解下苹果的商品种类。在苹果In-App Purchase Programming Guide文档上写明了,商品种类分为如下几种。接过GooglePlay支付的会发现,这点还是很相似的。
(1)消耗类商品
每次使用都须从新购买。
(2)非消耗类商品
购买一次即可。系统会自己购买状态,且会同步所有用户设备都一直保持可用状态。
(3)自动再生订阅
例如:一本书的章节内容。
(4)非自动再生订阅
例如:一个航班表。
(5)免费订阅
例如:报刊杂志等。

消耗类与非消耗类商品的区别:

订阅类商品的区别:

2.支付流程

对于IAP整个下单到支付过程,下图很形象的说明了该步骤:


(1) 应用向服务器发送请求,获得一份产品列表。
(2) 服务器返回包含商品标识符的列表。
(3) 应用向App Store发送请求,得到商品的信息。
(4) App Store返回商品信息。
(5) 应用把返回的商品信息显示在UI界面上。
(6) 用户选择某个商品。
(7) 应用向App Store发送支付请求。
(8) App Store处理支付请求并返回交易完成信息。
(9) 应用从信息中获得数据,并发送至服务器。
(10) 服务器纪录数据,并进行校验。
(11) 服务器将数据发给App Store来验证该交易的有效性。
(12) App Store对收到的数据进行解析,返回该数据和说明其是否有效的标识。
(13) 服务器读取返回的数据,确定用户购买的内容。
(14) 服务器将购买的内容传递给程序。

3.配置商品

(1)打开iTunes Connect后台
用开发者帐号,登录iTunes Connect,企业级用户需用主开发者帐号。

(2)配置iTunes Connect
在iTunes Connect后台添加应用,并配置App内购买项目,由于我们游戏中的钻石、金币等都属于消耗型商品,因此,直接选的这个。需注意下配置的Bundle id须和项目plist中的Bundle id一致。并添加沙箱测试帐号。

注意:商品Id不可重复,如果删除某个商品,以后这个商品的ID也不可用,即使它已经被删除了;另外类型也不能改,选错了只能重新增加一个商品。

iOS内支付接入

1. 项目工程引入StoreKit.framework
2. 这里推荐一个叫IAPHelper的开源封装,有效的封装支付流程,进一步简化了接入的效率。所以,下面也是基于该项目进行的接入。IAPHelper可自行Github搜索。

(1)InAppRageIAPHelper.m。在init中初始化商品id列表。

#import "InAppRageIAPHelper.h"
#import "InAppRageIAPHelper.h"

@implementation InAppRageIAPHelper
@synthesize orderInfo = _orderInfo;

static InAppRageIAPHelper * _sharedHelper;

+ (InAppRageIAPHelper *) sharedHelper {
    if (_sharedHelper != nil) {
        return _sharedHelper;
    }

    _sharedHelper = [[InAppRageIAPHelper alloc] init];
    return _sharedHelper;
}

- (void)dealloc
{
    [_orderInfo release];
    _orderInfo = nil;
    [super dealloc];
}

- (id)init {
    NSSet *productIdentifiers = [NSSet setWithObjects:
                                 @"com.game.test.10001",
                                 @"com.game.test.10002",
                                 @"com.game.test.10003",
                                 @"com.game.test.10004",
                                 @"com.game.test.10005",
                                 nil];
    
    if ((self = [super initWithProductIdentifiers:productIdentifiers])) {                
        
    }
    
    return self;
}

@end

(2)注册本地通知。一般在应用启动时,添加如下代码:(productsLoaded、productPurchased、productPurchaseFailed分别对应支付过程中三种加载中,支付完成,支付失败状态回调,可根据实际情况作对应的处理)

    [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(productsLoaded:) name:kProductsLoadedNotification object:nil];
    [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(productPurchased:) name:kProductPurchasedNotification object:nil];
    [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(productPurchaseFailed:) name:kProductPurchaseFailedNotification object:nil];
    [[InAppRageIAPHelper sharedHelper]requestProducts];

(3)发起支付。

    if ([SKPaymentQueue canMakePayments]) {
        [[InAppRageIAPHelper sharedHelper]buyProductIdentifier:[self getItemId] game_order:[self getOrderId]];
    } else {
        // 不允许程序内付费购买
        
    }

(4)支付成功的回调。这里将AppStore返回的数据,进行Base64加密,然后再发送给游戏服务器进行校验。同时,本地也会存储返回的票据receipt,防止在发送给服务器过程中请求失败,造成的充值成功但不到账的漏单现象。

-(NSData*)receiptWithTransation:(SKPaymentTransaction*) transcation {
    NSData *receipt = nil;
    if ([[NSBundle mainBundle]respondsToSelector:@selector(appStoreReceiptURL)]) {
        NSURL *receiptUrl = [[NSBundle mainBundle]appStoreReceiptURL];
        receipt = [NSData dataWithContentsOfURL:receiptUrl];
    } else {
        if ([transcation respondsToSelector:@selector(transactionReceipt)]) {
            receipt = [transcation transactionReceipt];
        }
    }
    
    return receipt;
}

-(void)productPurchased:(NSNotification*) notification {
    [NSObject cancelPreviousPerformRequestsWithTarget:self];
    SKPaymentTransaction *transaction = (SKPaymentTransaction*)notification.object;
    
    NSData *receipt = [self receiptWithTransation:transaction];
    NSString *base64Receipt = [receipt base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
    [[InAppRageIAPHelper sharedHelper].orderInfo setObject:[NSString stringWithFormat:@"%@", base64Receipt] forKey:@"originReceipt"];
    
    NSString *m_params = [self makeHttpParams:base64Receipt];
    if (m_params != nil) {
        [self saveReceipt:m_params];
        [self postGameServer:[self getPayUrl] params:m_params];
    }
}

(5)漏单检测。下次,启动时,会进行漏单检测。若存在本地票据receipt,就向游戏服务器发起请求。直到游戏服务器返回成功,再删除本地的票据receipt。

    NSUserDefaults *userDefalut = [NSUserDefaults standardUserDefaults];
    NSMutableDictionary *receiptDict = [NSMutableDictionary dictionaryWithDictionary:[userDefalut objectForKey:@"receipts"]];
    NSEnumerator *enumerator = [receiptDict objectEnumerator];
    for (NSObject *obj in enumerator) {
        [self postGameServer:[self getPayUrl] params:[NSString stringWithFormat:@"orderdata=%@",obj]];
    }

(6)由于,我们的校验是放在服务器进行的,所以,这里就不进行过多的介绍了。简单说下,App Store正式环境校验地址是https://buy.itunes.apple.com/verifyReceipt ,测试环境校验地址是:https://sandbox.itunes.apple.com/verifyReceipt

iOS支付安全问题

对于某些越狱设备来说,如果校验流程有漏洞的话,使用某些神器,就能绕过Appstore的付费流程,伪造订单,达到免支付体验各种付费功能。
其中列举如下神器:
(1)ap cracker:越狱软件可以截获付费请求,并直接返回付费成功。
(2)iap free: 截获付费请求的同时,还能截获客户端发起的验证请求 ,返回验证成功的数据 ,返回的数据和官方的数据并不是完全一样,可以识别出来是否作弊,但是不保证永久有效。

因此,首先在支付成功之后,要将支付成功返回的票据发送给服务器,在服务器端作验证,根据服务器的验证结果来做相应的处理。其次,本地对应伪造的票据进行过滤。

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

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,495评论 25 707
  • 一、清洁 1、如果有的牙缝会经常塞东西,那就一定要用牙线。因为频繁塞东西很容易出现龋齿。并且一旦出现龋齿就要去补,...
    潇湘妃子JC阅读 189评论 0 0
  • 早起准备行李在爸妈催促下很早很早去了北京南站十一真的不是一般的人多A大——北京南(转悠了很久商店)——天津西——公...
    dq920813阅读 139评论 0 0
  • 我感觉我被它盯上了。 暗处的那双眼睛,带着幽蓝的瞳色,眼神里仿佛是一种诱惑,一种告诉你前面就是无底的深渊,却还是自...
    写手李文优阅读 288评论 0 5