首先我们知道什么是内购?
- 内购就是在我们的App中能够购买东西
- 当然购买东西包括手机,毛绒玩具等日常用品,也包括购买虚拟的例如双倍经验卡,大血瓶等.
- 那么什么时候使用内购呢?
我们还需要知道苹果公司的盈利模式:
通过苹果应用程序商店有三种主要赚钱的方式:
1. 直接收费(与国内大部分用户 的消费习惯相悖)
1.1 广告
1.2 O2O -> Online推广 & Offline交易,闭环
2. 内购:应用程序本身的增值产品,游戏装备,应用程序中增值功能同样可以内购
内购:三(苹果)七(开发商)开
3.第三方支付:跟应用程序无关的
苹果不能从中获利
所以.当我们在应用中有销售行为的话,能不走内购就不走内购,但是增值业务的话,必须是走内购流程,否则在上架App Store的时候是会被拒绝的.
但是,我们如果是做电商类App,那么久不要走内购了,因为你的产品销量越大,你就亏的越多,因为App开发商还要给苹果30%的分红,哦?不,是苹果先拿到钱,然后再给你70%的分红.
so,内购主要是针对APP内置的增值服务的,就是虚拟商品,一把屠龙宝刀,不就是一张图片吗?所以苹果要分红.那么App做内购的步骤有哪些呢?
1.首先你要有一个开发者账号 需要99$.
2.你要有一部 iOS 真机,未越狱版本,越狱版不能测试,只能购买
3.你需要在 苹果开发者网站 配置相关证书 .cer 和 .mobileprovision证书 配置App相关内购信息
3.1 -- 配置App(必须要有一些消耗品或者非消耗品), AppID 不能使用统配符. (越狱手机不能测试内购,但是能购买内购商品).
3.1.1 --设置方式: 在 iTunes Connect 网站里, ->"App Store" 添加内购APP, 然后再进入”功能"(设置 内购产品,:非消耗型项目, 一次性消费型项目)
3.2 -- 测试账号: 不用真的花钱.
3.2.1 -- 设置方式: “功能”—>” “在用户和职能”—> “沙箱测试员” —>添加测试账号" .这个邮箱瞎写,但是密码要记住
3.3 -- 配置账户信息 借记卡/信用卡都行
3.3.1--设置方式: “功能”—>”协议\税务和银行业务” (都是老板的信息,钱由苹果公司打给老板)
3.4 -- 在回到 App Store 里将刚刚那些 设置的Appde内购项目添加到 你的App里
很重要却很容易忽视的细节
>>> 在使用未越狱的真机测试内购的时候,因为使用的是测试账号,是真机.
>>>所以需要在真机的 “设置”—> “iTunes Store 和 App Store” —>注销真实的账号(否则就真扣钱了) —>换成 刚刚在develop网站里填写的 内购测试账号
>代码是敲不完的,我们学的是思路.
>代码源于生活,又高于生活.
那么就让我们结合实际商场购物来理一下我们写代码思路:
1> App 想苹果服务器请求可销售商品
2> 代理方法获得可销售商品
3> 展示商品(tableView等)
4>用户选择商品
5> 根据用户选择的商品,给用户小票(商品转payment类型),那小票去排队
6> 监听 交易队列变化
7> 用户去交钱,我们监听用户付款状态
8> 付款成功, 我们给用户相应的增值服务
9> 结束交易 (否则用户下一次进来还是在交易)
下面我们来上代码:
//内购需要导入框架
#import <StoreKit/StoreKit.h>
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
//需要在 APP中 设置 bundel ID 和 在 网站注册时候一样.
// 有内购需求的App 需要 安装在App里按钮 苹果develop 网站里面配置好 ios_development.cer 和 xxx.mobileprovision(配置文件),只要双击一下,一闪就算安装好了
}
0>首先:我们需要读取JSON文件
//0 需要获得可销售商品(就是我们的APP内购商品的 productsID)
NSString * productPath = [[NSBundle mainBundle] pathForResource:@"products.json" ofType:nil];
//把JSON 转为 data
NSData *jsonData = [NSData dataWithContentsOfFile:productPath];
//反序列化
NSArray *productArr = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:nil];
NSLog(@"%@",productArr);
//我们需要取出 [{productId:"xxxx"}] 数组里的字典里面的 key 对应的Value值
//KVC 取值
NSArray *tempArr2 = [productArr valueForKey:@"productId"];
NSLog(@"%@",tempArr2);
1>我们想苹果发送销售App内购商品的请求
//1>App向苹果请求可销售商品
SKProductsRequest * proRequest = [[SKProductsRequest alloc]initWithProductIdentifiers:[NSSet setWithArray:tempArr2]];
2>通过代理获得 苹果返回的课销售商品
//2 >获取可销售的 商品 (通过代理)
//2.1
proRequest.delegate = self;
//2.2 需要开始请求
[proRequest start];
/** 2.3
* 当获取到苹果允许销售的商品 时候调用
*
* @param request 销售内购商品的请求
* @param response 苹果对我的请求的返回的响应 (里面有产品数组,以及不允许销售商品的 ID)
*/
-(void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{
//遍历,获得每一个可销售商品的信息
for (SKProduct *product in response.products) {
NSLog(@"产品价格%@---产品ID %@---产品标题 %@----产品描述 %@",product.price,product.productIdentifier,product.localizedTitle,product.localizedDescription);
}
//给全局数组赋值,里面装可销售 商品
self.productsArray = response.products;
//赋值后,需要刷新数据源
[self.tableView reloadData];
}
//2.4 还要在上面遵守代理
@interface ViewController ()<SKProductsRequestDelegate>
//2.5 并且需要赋值一个数组,所以我们上面定义一个数组
/**
* 装所有 可销售内购商品的数组
*/
@property (nonatomic, strong) NSArray * productsArray;
3>展示数据(可销售的内购商品)
//3 通过tableview展示数据 可销售商品的数据
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
return 1;
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return self.productsArray.count;
}
//3.1
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *ID = @"storeCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:ID];
}
SKProduct *product = self.productsArray[indexPath.row];
cell.textLabel.text = product.localizedTitle;
// cell.detailTextLabel.text = product.price.stringValue;
cell.detailTextLabel.text = [NSString stringWithFormat:@"%@",[product.price description]];
return cell;
}
3 补充 :上面给cell赋值时候,不注意引起Crash.分享如下:
/*引起BUG源代码:
cell.detailTextLabel.text = product.price;--->引起Crash.报错如下 是因为price类型是 NSDecimalNumber.
报错如下:
reason: '-[NSDecimalNumber isEqualToString:]: unrecognized selector sent to instance 0x7fa11ad28f10'
更改方式1: cell.detailTextLabel.text = product.price.stringValue;
更改方式2: cell.detailTextLabel.text = [NSString stringWithFormat:@"%@",[product.price description]];
(更改方式1/2 留下一个就行.)
*/
```
> 4> 用户选择购买那些增值服务
//4 用户选择商品(屠龙刀 ,大药瓶等), 代理方法
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
// 4.1 取出用户选择的产品
SKProduct *product = self.productsArray[indexPath.row];
}
> 5> 用户拿小票排队等待付款
````
//5.1 拿商品转换类型 (根据商品是什么样的,商家给用户小票,让用户拿着小票去排队付款)
SKPayment * payment = [SKPayment paymentWithProduct:product];
//5 . 排队,等付款
[[SKPaymentQueue defaultQueue] addPayment:payment];
````
> 6> 监听付款队列的变化
````
//6 监听 付款队列的变化,增加一个监听者,遵守SKPaymentTransactionObserver 协议就行
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
//6.1在上面遵守协议
@interface ViewController ()<SKProductsRequestDelegate,SKPaymentTransactionObserver>
````
>7 监听付款队列的变化 轮到自己付款.
````
/**
* 7. 监听付款队列发生了变化时候调用 直白:更新付款队列的情报
*
* @param queue 付款队列 交易队列
* @param transactions 所有的交易(别人也在交易,存在许多交易在队列里,放进一个数组里)
*/
-(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions{}
````
>8 > 根据付款的状态,提供对应的增值服务
>9 > 如果交易结束,关闭交易
````
/*
SKPaymentTransactionStatePurchasing, // 交易正在被加入队列
SKPaymentTransactionStatePurchased, // 交易在队列中, 用户已经付款.客户端需要完成交易
SKPaymentTransactionStateFailed, // 交易失败
SKPaymentTransactionStateRestored, // 交易从交易历史中被恢复. 客户端需要完成交易.
SKPaymentTransactionStateDeferred // 交易暂时不确定. 加入交易队列了,在犹豫要不要付款. iOS8新增
*/
-(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions{
//遍历所有交易,得出没有交易的状态
for (SKPaymentTransaction * payTran in transactions) {
if(payTran.transactionState == SKPaymentTransactionStatePurchased){
//8. 用户付款成功,我们提供相应增值服务.
NSLog(@"用户已经付款,我方App给他提供增值服务");
//9 结束交易. 操作队列结束本次 交易.
[[SKPaymentQueue defaultQueue] finishTransaction:payTran];
}
if(payTran.transactionState == SKPaymentTransactionStateRestored){
//8. 用户付款成功,我们提供相应增值服务.
NSLog(@"用户已经付款,我方App给他提供增值服务");
//9 结束交易. 操作队列结束本次 交易.
[[SKPaymentQueue defaultQueue] finishTransaction:payTran];
}
//增加一个判断.如果失败,失败的原因是什么(不是用户主动取消的情况)
if (payTran.transactionState == SKPaymentTransactionStateFailed) {
if (payTran.error.code != SKErrorPaymentCancelled) {
NSLog(@"交易失败: %@", payTran.error.localizedDescription);
}
}
}
}
````
> 10 用户如果换手机了,怎么办?之前购买过的可以设置一键恢复
````
// 10 :一键 恢复购买装备 (例如换手机了)
//在viewdidLoad里增加一个复位按钮
- (void)viewDidLoad {
[super viewDidLoad];
self.navigationItem.title = @"测试";
UIBarButtonItem *restoreButton = [[UIBarButtonItem alloc]initWithTitle:@"恢复购买" style:UIBarButtonItemStylePlain target:self action:@selector(restoreButtonClick:)];
self.navigationItem.leftBarButtonItem = restoreButton;
}
-(void)restoreButtonClick:(UIButton *)sender{
// 付款队列 恢复 已经完成的 所有交易
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
// 付款队列 恢复 已经完成的 所有交易 需要应用程序的 用户名. (用户可能有3个号)(用户填写用户名去后台验证密码)
// [[SKPaymentQueue defaultQueue] restoreCompletedTransactionsWithApplicationUsername:<#(nullable NSString *)#>];
}
````
###由于时间仓促,以及个人能力有限,不足之处.或者哪里有不同意见.好的想法还希望不吝告知.共同进步.码字不容易啊!
##最后:源码在[我的iOS in_App Purchases(内购)](https://github.com/HHQBOOK/in_App_Purchases)