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