IAP内购

服务器模式:

使用这种方式,要提供另外的服务器将产品发送给程序。 服务器交付适用于订阅、内容类商品和服务,因为商品可以作为数据发送,而不需改动程序束。

例如,一个游戏提供的新的内容(关卡等)。 Store Kit不会对服务器端的设计和交互做出定义,这方面工作需要你来完成。 而且,Store

Kit不提供验证用户身份的机制,你需要来设计。 如果你的程序需要以上功能,例如,纪录特定用户的订阅计划, 你需要自己来设计和实现。

服务器类型的购买过程

1. 程序向服务器发送请求,获得一份产品列表。

2. 服务器返回包含产品标识符的列表。

3. 程序向App Store发送请求,得到产品的信息。

4. App Store返回产品信息。

5. 程序把返回的产品信息显示给用户(App的store界面)

6. 用户选择某个产品

7. 程序向App Store发送支付请求

8. App Store处理支付请求并返回交易完成信息。

9. 程序从信息中获得数据,并发送至服务器。

10. 服务器纪录数据,并进行审(我们的)查。

11. 服务器将数据发给App Store来验证该交易的有效性。

12. App Store对收到的数据进行解析,返回该数据和说明其是否有效的标识。

13. 服务器读取返回的数据,确定用户购买的内容。

14. 服务器将购买的内容传递给程序。

Apple建议在服务器端存储产品标识,而不要将其存储在plist中。 这样就可以在不升级程序的前提下添加新的产品。

在服务器模式下, 你的程序将获得交易(transaction)相关的信息,并将它发送给服务器。服务器可以验证收到的数据,并将其解码以确定需要交付的内容。 这个流程将在“验证store收据”一节讨论。

对于服务器模式,我们有安全性和可靠性方面的顾虑。 你应该测试整个环境来避免威胁。《Secure Coding Guide》文档中有相关的提示说明。

虽然非消耗性商品可以用内置模式来恢复,订阅类商品必须通过服务器来恢复。你要负责纪录订阅信息、恢复数据。 消耗类商品也可以通过服务器方式来纪录。例如,由服务器提供的一项服务, 你可能需要用户在多个设备上重新获得结果。

苹果服务端配置指南:

使用IAP内购的准备工作。通常需要经过以下几个步骤(下面的准备工作是针对真机的Provisioning Profile配置过程,模拟器无法测试IAP内购):

1.在苹果开发者中心创建支持IAP服务的App ID并指定具体的Bundle ID,假设是“com.tj.xxx”(注意这个Bundle ID就是日后要开发的游戏的Bundle ID)。

2.基于“com.tj.xxx”创建开发者配置文件(或描述文件)并导入对应的设备(创建过程中选择支持IAP内购服务的App ID,这样iOS设备在运行指定Boundle ID应用程序就知道此应用支持IAP内购服务)。

3.在iTunes Connect中创建一个应用(假设叫“IAPTest”,这是一款含有内购的游戏)并指定“套装ID”为之前创建的“com.tj.xxx”,让应用和这个App关联(注意这个应用不需要提交)。

4.在iTunes Connect的“用户和职能”中创建沙盒测试用户。(测试阶段用沙盒用户可以进行购买,购买任何东西不用担心被扣钱)。

5.到iTuens Connect中设置“App 内购买项目”,这里仍然以上面的“IAPest”项目为例,假设这个游戏中有一种道具,为“能量瓶”(为玩家提供能量),@“能量瓶”属于消耗品,用完一次必须再次购买。

6.到iTunes Connect中找到“协议、税务和银行业务”增加“iOS Paid Applications”协议,并完成所有配置后等待审核通过(注意这一步如果不设置在应用程序中无法获得可购买产品)。

在iOS“设置”中找到”iTunes Store与App Store“,在这里可以选择使用沙盒用户登录或者处于注销状态,但是一定注意不能使用真实用户登录,否则下面的购买测试不会成功,因为到目前为止我们的应用并没有真正通过苹果官方审核,所以只能用沙盒测试用户。

7.有了上面的设置之后保证应用程序Bundle ID和iTunes Connect中的Bundle ID(或者说App ID中配置的Bundle ID)一致即可准备开发。

ios客户端

#import

@interface JarIAPManager ()

{

id _observer;

}

@end

@implementation JarIAPManager

+ (instancetype)defaultManager{

static JarIAPManager *defaultManager = nil;

static dispatch_once_t onceToken = 0;

dispatch_once(&onceToken, ^{

defaultManager = [[JarIAPManager alloc]  init];

});

return defaultManager;

}

- (instancetype)init{

self = [super init];

if (self) {

[[SKPaymentQueue defaultQueue] addTransactionObserver:self];

_observer = [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidEnterBackgroundNotification object:self queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {

for (SKPaymentTransaction * paymentTransaction in [SKPaymentQueue defaultQueue].transactions) {

[[SKPaymentQueue defaultQueue] finishTransaction:paymentTransaction];

}

}];

}

return self;

}

- (void)dealloc{

[[SKPaymentQueue defaultQueue] removeTransactionObserver:self];

[[NSNotificationCenter defaultCenter] removeObserver:_observer];

_observer = nil;

}

-(BOOL)iapEnable{

return [SKPaymentQueue canMakePayments];

}

#pragma mark --purchase product

//根据产品标识符去购买产品--[购买结果]

- (void)purchaseProductWithIdenfifier:(NSString *)productIdentifier Order:(NSString *)Order{

//该字符串标识一个特定的产品和用户原意购买的数量

SKMutablePayment * payment = [[SKMutablePayment alloc] init];

payment.productIdentifier = productIdentifier;

NSData *datas = [Order dataUsingEncoding:NSUTF8StringEncoding];

payment.requestData =datas;

[[SKPaymentQueue defaultQueue] addPayment:payment];

}

//SKPaymentTransactionObserver协议---[更常用的做法还是等待支付队列告知交易状态的更新。]

- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions{

__weak typeof(self)weakSelf = self;

for (SKPaymentTransaction * paymentTransaction in transactions) {

//小票状态--》支付交易的状态

switch (paymentTransaction.transactionState) {

case SKPaymentTransactionStatePurchasing:

{

NSLog(@"JarIAPManager: Transaction is being added to the server queue.");

}

break;

case SKPaymentTransactionStatePurchased:

{

NSLog(@"JarIAPManager: Transaction is in queue, user has been charged.  Client should complete the transaction.");

[weakSelf purchaseSuccessForTransaction:paymentTransaction];

}

break;

case SKPaymentTransactionStateFailed:

{

NSLog(@"JarIAPManager: Transaction was cancelled or failed before being added to the server queue.");

[weakSelf purchaseFailedForTransaction:paymentTransaction];

}

break;

case SKPaymentTransactionStateRestored:

{

NSLog(@"JarIAPManager: Transaction was restored from user's purchase history.  Client should complete the transaction.");

[[SKPaymentQueue defaultQueue] finishTransaction:paymentTransaction];

}

default:

break;

}

}

}

执行

- (void)purchaseFailedForTransaction:(SKPaymentTransaction *)transaction{

NSLog(@"失败流水--》%@",transaction.transactionIdentifier);

if (transaction != nil) {

[[Toast makeText:@"交易取消。" duration:3000] show:NO];

}

[[SKPaymentQueue defaultQueue] finishTransaction:transaction];

}

- (void)purchaseSuccessForTransaction:(SKPaymentTransaction *)transaction{

__weak typeof(self)weakSelf = self;

// 验证凭证,获取苹果返回的交易凭证

// appStoreReceiptURL iOS7.0增加的,购买交易完成后,会将凭证存储在该地址

NSURL * receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];

// 从沙盒中获取到购买凭证

NSData * receiptData = [NSData dataWithContentsOfURL:receiptURL];

//base64加密

NSString *encodeStr = [receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];

//打包成字典

NSMutableDictionary * parDic = [NSMutableDictionary dictionaryWithCapacity:3];

NSString *Order = [[NSString alloc] initWithData:transaction.payment.requestData encoding:NSUTF8StringEncoding];

DICT_SET_STRING(Order,TAG_ORDER, parDic);

DICT_SET_STRING(encodeStr, TAG_RECEIPT_DATA, parDic);

NSLog(@"下单成功的Order--》%@",Order);

if (Order !=NULL) {

//提前缓存

[[JarIAPSqliteService sharedInstance] insertTestListTransactionWithDic:parDic];

//        //检测是否已经存储过了

//        NSMutableArray *getTestDatas = [[NSMutableArray alloc] init];

//        getTestDatas = [[JarIAPSqliteService sharedInstance] getTestList];

//        NSLog(@"存储后的Order个数-->%ld",(unsigned long)getTestDatas.count);

//        if (getTestDatas.count != 0) {

//            for (NSMutableDictionary * dic in getTestDatas) {

//                NSLog(@"存储后的Order---》%@",dic[TAG_ORDER]);

//            }

//        }else{

//            NSLog(@"存储为0.");

//        }

//发送到SDK服务器

[[LoginDataSource sharedInstance] verifyThePurchaseWithDictData:parDic completion:^(NSDictionary *resultData, NSError *error) {

if ([resultData[TAG_RESULT] intValue] == 0 || Order.length != 0) {

[weakSelf actionWithResult:resultData OrderStr:eOrder];//执行删除

}

}];

}

[[SKPaymentQueue defaultQueue] finishTransaction:transaction];

}

参考链接:

http://mobile.51cto.com/iphone-410162.htm  比较全的文档介绍

http://www.cocoachina.com/ios/20150129/11068.html demo

http://www.2cto.com/kf/201504/389224.html 有订阅

http://blog.csdn.net/xingchen1106/article/details/45477433

http://blog.jobbole.com/38032/ 唐巧介绍安全

http://blog.csdn.net/fly_fish456/article/details/8955871

http://www.tairan.com/archives/2215/

【后期要理解的安全性,以及沙盒和正式环境】

http://www.360doc.com/content/14/1113/15/12282510_424834793.shtml

http://www.cocoachina.com/special/iap.html

http://www.2cto.com/kf/201504/389224.html

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

推荐阅读更多精彩内容

  • 首先,用户在购买产品时,应用程序从应用商店获取该产品的信息,把商店 界面提供给用户,然后让用户选择产品,如下图中第...
    Dosun阅读 7,467评论 0 3
  • 购买过程的最后一部分是应用程序等待应用商店处理支付请求,存储本次购买的信息以便将来启动,下载购买的内容,然后标记交...
    Dosun阅读 1,053评论 0 1
  • 1 、在iTunes Connect中,每个应用程序可以创建多少格内置购买产品 ID ? 阅读 In-App Pu...
    Dosun阅读 3,804评论 0 0
  • 在我们应用开发中我们经常在自己的项目中使用到支付,下面我们来谈谈iOS这块的支付;iOS支付主要分为两类,第三方支...
    Hither阅读 8,111评论 9 42
  • 介绍 分享下自己做内购遇到问题,总结出来的经验。 接入 使用RMStore。 1、使用RMStore的接入步骤 2...
    落影loyinglin阅读 6,079评论 31 21