iOS 内购的实现

自己开发的视频直播项目,牵涉到充值金币,用到了苹果公司的内购,趴坑了两天,这里总结下实现苹果内购。

一. 创建测试App

首先你需要登录 App的ItunesConnection,你会看到如下界面

简单的介绍一下这几个选项

1.我的App主要用于管理自己的App应用,例如编辑资料,上架,下架等。

2.销售和趋势主要是来查看App在各个平台的下载量,收入等方面数据,里面有曲线图等图文结合的方式给我们参考。

3.付款和财务报告显示的是你的收入以及付款等相关信息。

4.iAd主要是跟广告有关,开发者可以登录到Workbench,通过iAd对应用的广告进行控制。

5.用户和职能用于生成相应账号,例如苹果沙河测试账号。

6.协议,税务和银行业务则是你银行相关账户的信息设置。

在这里我们选择第一个选项,我的App, 然后点击左上角的加号,新建一个用来测试用的App。

点新建 App,会出现新建窗口;

在这里有几个需要填写的地方,名称自己取,平台IOS,语言选择了简体中文,套装ID也就是你的Bundle Identifier,需要你在Certificates页面申请BundleID,SKU可以理解为用户看一看到的唯一标示,会体现在你的app的App Store的链接中。

二.添加内购

App创建好之后,我们打开创建的App,在左上角选择功能,会看到左侧的App 内购买项目。我们点击右下角的加号,为App添加内购项目。

之后我们会看到类型的选项,如下图

官方的注释写的很清楚了,只在这里简单的说下前两种:

- 消耗型项目 就像你玩游戏需要买金币,买钻石等,只要花钱就可以无限次的购买

- 非消耗型项目 就像你在App Store购买App,买了一次之后就不用再买第二次,你拥有永久使用权。

在我们的app中,是充值会员,所以选择的是第一种,可以无限次购买。

这里有几个选项,需要填写商品名称,产品ID以及价格等级,简单说明一下

1. 商品名称根据你的消费道具的实际意义来说明,比如“100颗宝石”,“100金币”等。

2. 产品ID是比较重要的,由项目自定义,只要唯一即可,因为测试,我在这里随便填写的123,在实际应用中,一定要认真填写。

3. 价格等级的话“查看价格表”中有对应的说明,可以对照着表中每个国家的货币价格与等级来选择

接下来是语言选择,和上传快照如下图


点击添加语言,填写名称和描述,这里我们依然选择简体中文,如下

审核备注,根据实际情况填写,可以不填。而下面的屏幕快照,则是商品图片,以像素为单位,最低尺寸为321,390,尺寸需求如下图,上传即可。

到这里为止, 我们的内购项目则添加完成。接下来则是测试阶段了。

三.申请沙盒测试账号(用来测试购买项目)

这个账号,是利用苹果的沙盒测试环境来模拟AppStore的购买流程,你肯定不会想要用真实RMB去购买测试吧?

首先我们回到iTunes Connect中,在这里我们选择用户和职能。

然后在上面的第三个选项沙箱技术测试员中点击加号,添加测试员。

在信息填写页面只简单说两句。

所有信息都可以随意填写,不用管是否真实。

App Store地区选择,一定要选对,它对应的是你创建的App的地区, 你App是中国的话, 在这里我们依然选择中国。

此账号只能用来测试,不要在正式的appstore上使用

填写完毕,点击保存后,我们则生成一个测试账号,当然这个账号是可以随时删除和添加的。

之后终于到了写代码的时候了

首先要导入StoreKit框架

大部分代码都可以在.m文件中实现。

#import  "ViewController.h"

#import  <StoreKit/StoreKit.h>

@interfaceViewController ()<SKPaymentTransactionObserver,SKProductsRequestDelegate>

@property (nonatomic,copy) NSString *currentProId;

@end

@implementation  ViewController

- (void)viewDidLoad {

[super viewDidLoad];

UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];

button.frame = CGRectMake(100, 100, 100, 100);

button.backgroundColor = [UIColor greenColor];

[button setTitle:@"6元" forState:UIControlStateNormal];

[button addTarget:self action:@selector(btnClick:) forControlEvents:UIControlEventTouchDown];

[self.view addSubview:button];

}

- (void)btnClick:(UIButton *)button{

[[SKPaymentQueue defaultQueue] addTransactionObserver:self];

_currentProId = @"123";

if([SKPaymentQueue canMakePayments]){

[self requestProductData:product];

}else{

NSLog(@"不允许程序内付费");

}

}

//去苹果服务器请求商品

- (void)requestProductData:(NSString *)type{

NSLog(@"-------------请求对应的产品信息----------------");

[SVProgressHUD showWithStatus:nil maskType:SVProgressHUDMaskTypeBlack];

NSArray *product = [[NSArray alloc] initWithObjects:type,nil];

NSSet *nsset = [NSSet setWithArray:product];

SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:nsset];

request.delegate =self;

[request start];

}

//收到产品返回信息

- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{

NSLog(@"--------------收到产品反馈消息---------------------");

NSArray *product = response.products;

if([product count] == 0){

[SVProgressHUD dismiss];

NSLog(@"--------------没有商品------------------");

return;

}

NSLog(@"productID:%@", response.invalidProductIdentifiers);

NSLog(@"产品付费数量:%lu",(unsigned long)[product count]);

SKProduct *p =nil;

for (SKProduct *pro in product) {

NSLog(@"%@", [pro description]);

NSLog(@"%@", [pro localizedTitle]);

NSLog(@"%@", [pro localizedDescription]);

NSLog(@"%@", [pro price]);

NSLog(@"%@", [pro productIdentifier]);

if([pro.productIdentifier isEqualToString:_currentProId]){

p = pro;

}

}

SKPayment *payment = [SKPayment paymentWithProduct:p];

NSLog(@"发送购买请求");

[[SKPaymentQueue defaultQueue] addPayment:payment];

}

//请求失败

- (void)request:(SKRequest *)request didFailWithError:(NSError *)error{

[SVProgressHUD showErrorWithStatus:@"支付失败"];

NSLog(@"------------------错误-----------------:%@", error);

}

- (void)requestDidFinish:(SKRequest *)request{

[SVProgressHUD dismiss];

NSLog(@"------------反馈信息结束-----------------");

}

//沙盒测试环境验证

#define SANDBOX @"https://sandbox.itunes.apple.com/verifyReceipt"

//正式环境验证

#define AppStore @"https://buy.itunes.apple.com/verifyReceipt"

/**

*验证购买,避免越狱软件模拟苹果请求达到非法购买问题

*

*/

-(void)verifyPurchaseWithPaymentTransaction{

//从沙盒中获取交易凭证并且拼接成请求体数据

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

NSData *receiptData=[NSData dataWithContentsOfURL:receiptUrl];

NSString *receiptString=[receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];//转化为base64字符串

NSString *bodyString = [NSString stringWithFormat:@"{\"receipt-data\" : \"%@\"}", receiptString];//拼接请求数据

NSData *bodyData = [bodyString dataUsingEncoding:NSUTF8StringEncoding];

//创建请求到苹果官方进行购买验证

NSURL *url=[NSURL URLWithString:SANDBOX];

NSMutableURLRequest *requestM=[NSMutableURLRequest requestWithURL:url];

requestM.HTTPBody=bodyData;

requestM.HTTPMethod=@"POST";

//创建连接并发送同步请求

NSError *error=nil;

NSData *responseData=[NSURLConnection sendSynchronousRequest:requestM returningResponse:nil error:&error];

if (error) {

NSLog(@"验证购买过程中发生错误,错误信息:%@",error.localizedDescription);

return;

}

NSDictionary *dic=[NSJSONSerialization JSONObjectWithData:responseData options:NSJSONReadingAllowFragments error:nil];

NSLog(@"%@",dic);

if([dic[@"status"] intValue]==0){

NSLog(@"购买成功!");

NSDictionary *dicReceipt= dic[@"receipt"];

NSDictionary *dicInApp=[dicReceipt[@"in_app"] firstObject];

NSString *productIdentifier= dicInApp[@"product_id"];//读取产品标识

//如果是消耗品则记录购买数量,非消耗品则记录是否购买过

NSUserDefaults *defaults=[NSUserDefaults standardUserDefaults];

if ([productIdentifier isEqualToString:@"123"]) {

int purchasedCount=[defaults integerForKey:productIdentifier];//已购买数量

[[NSUserDefaults standardUserDefaults] setInteger:(purchasedCount+1) forKey:productIdentifier];

}else{

[defaults setBool:YES forKey:productIdentifier];

}

//在此处对购买记录进行存储,可以存储到开发商的服务器端

}else{

NSLog(@"购买失败,未通过验证!");

}

}

//监听购买结果

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

for(SKPaymentTransaction *tran in transaction){

switch (tran.transactionState) {

case SKPaymentTransactionStatePurchased:{

NSLog(@"交易完成");

// 发送到苹果服务器验证凭证

[self verifyPurchaseWithPaymentTransaction];

[[SKPaymentQueue defaultQueue] finishTransaction:tran];

}

break;

case SKPaymentTransactionStatePurchasing:

NSLog(@"商品添加进列表");

break;

case SKPaymentTransactionStateRestored:{

NSLog(@"已经购买过商品");

[[SKPaymentQueue defaultQueue] finishTransaction:tran];

}

break;

case SKPaymentTransactionStateFailed:{

  NSLog(@"交易失败");

  [[SKPaymentQueue defaultQueue] finishTransaction:tran];

  [SVProgressHUD showErrorWithStatus:@"购买失败"];

}

break;

default:

break;

}

}

}

//交易结束

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

NSLog(@"交易结束");

[[SKPaymentQueue defaultQueue] finishTransaction:transaction];

}

- (void)dealloc{

[[SKPaymentQueue defaultQueue] removeTransactionObserver:self];

}

- (void)didReceiveMemoryWarning {

[super didReceiveMemoryWarning];

// Dispose of any resources that can be recreated.

}

@end

在这里需要注意几点,

1. 代码中的_currentProId所填写的是你的购买项目的的ID,这个和第二步创建的内购的productID要一致;本例中是 123。

2. 在监听购买结果后,一定要调用[[SKPaymentQueue defaultQueue] finishTransaction:tran];来允许你从支付队列中移除交易。

3. 沙盒环境测试appStore内购流程的时候,请使用没越狱的设备。

4. 请务必使用真机来测试,一切以真机为准。

5. 项目的Bundle identifier需要与您申请AppID时填写的bundleID一致,不然会无法请求到商品信息。

6. 真机测试的时候,一定要退出原来的账号,才能用沙盒测试账号

7. 二次验证,请注意区分宏, 测试用沙盒验证,App Store审核的时候也使用的是沙盒购买,所以验证购买凭证的时候需要判断返回Status Code决定是否去沙盒进行二次验证,为了线上用户的使用,验证的顺序肯定是先验证正式环境,此时若返回值为21007,就需要去沙盒二次验证,因为此购 买的是在沙盒进行的。

附:苹果支付错误目录

Status Code

Description

21000

The App Store could not read the JSON object you provided.

21002

The data in the receipt-data property was malformed or missing.

21003

The receipt could not be authenticated.

21004

The shared secret you provided does not match the shared secret on file for your account.Only returned for iOS 6 style transaction receipts for auto-renewable subscriptions.

21005

The receipt server is not currently available.

21006

This receipt is valid but the subscription has expired. When this status code is returned to your server, the receipt data is also decoded and returned as part of the response.Only returned for iOS 6 style transaction receipts for auto-renewable subscriptions.

21007

This receipt is from the test environment, but it was sent to the production environment for verification. Send it to the test environment instead.

21008

This receipt is from the production environment, but it was sent to the test environment for verification. Send it to the production environment instead.

摘自:http://blog.csdn.net/darling_shadow/article/details/51538267

参考:

1.iOS开发之内购-AppStore;

2.苹果官方文档

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

推荐阅读更多精彩内容

  • iOS应用如果涉及到支付功能,分为两类:第三方支付和苹果内购。那么什么情况下选择使用第三方支付,又在什么情况下选择...
    ZfRee阅读 38,837评论 36 66
  • - 消耗型项目 就像你玩游戏需要买金币,买钻石等,只要花钱就可以无限次的购买 - 非消耗型项目 就像你在App S...
    tiger2016阅读 650评论 0 1
  • iOS应用内付费(IAP)开发步骤 1.苹果iTunes Connect内购产品信息录入。 1)创建app内购买项...
    MillerWang阅读 10,981评论 0 7
  • 服务器模式: 使用这种方式,要提供另外的服务器将产品发送给程序。 服务器交付适用于订阅、内容类商品和服务,因为商品...
    水晶兰阅读 1,580评论 1 3
  • 一心向善,心存美好,不管遇到什么事情,总是用积极乐观的思维去思考问题,看待问题,不要用龌龊的思维去揣摩别人,比如跳...
    北方de雪阅读 318评论 0 0