参考链接
iOS开发之内购-AppStore
iOS App提交指南(二)-协议、税务和银行业务
iOS应用内付费(IAP)开发步骤列表
一、创建App
首先你需要登录 App的ItunesConnection,你会看到如下界面
简单的介绍一下这几个选项
- 我的App主要用于管理自己的App应用,例如编辑资料,上架,下架等。
- 销售和趋势主要是来查看App在各个平台的下载量,收入等方面数据,里面有曲线图等图文结合的方式给我们参考。
- 付款和财务报告显示的是你的收入以及付款等相关信息。
- iAd主要是跟广告有关,开发者可以登录到Workbench,通过iAd对应用的广告进行控制。
- 用户和职能用于生成相应账号,例如苹果沙河测试账号。
- 协议,税务和银行业务则是你银行相关账户的信息设置。
在这里我们选择第一个选项,我的App,然后点击左上角的加号,新建一个用来测试用的App。
点新建 App,会出现新建窗口;
在这里有几个需要填写的地方,名称自己取,平台IOS,语言选择了简体中文,套装ID也就是你的Bundle Identifier,需要你在Certificates页面 申请BundleID,SKU可以理解为用户看一看到的唯一标示,会体现在你的app的App Store的链接中。
申请BundleID
打开Certificates页面,在左侧选择Identifiers,并点击加号,申请一个新的Identifiers。
在这里Name可以随意填写,我填写的是TestAppStroeTestDemo,而用来使用的BundleID,我们在这里必须选择第一个选项唯一的,不用选择通配。在下面的选项中, 我们只需要勾选一个 Apple Pay即可,其他选项看自己需求,我在这里只选择了它。
之后我们回到创建App,选择好自己刚创建的 BundleID ,填写SKU, SKU是你App的专用ID,我在这里随意填写,直接复制了App名。点击创建,我们的测试App则创建成功。
二、协议、税务和银行业务
进入协议、税务和银行业务页面
进入协议、税务和银行业务页面后,会有3种合同类型,如果你之前没有主动申请过去合同,那么一般你现在激活的合同只有iOS Free Application一种。
页面内容分为两块:
- Request Contracts(申请合同)
- Contracts In Effect(已生效合同)。
合同类型分为3种:
- iOS Free Application(免费应用合同)
- iOS Paid Application(付费应用合同)
- iAd App NetNetwork(广告合同)
笔者暂时只申请过付费应用合同,所以下面主要讲一下付费应用合同的申请流程。
当我们点击申请iOS Paid Application合同后,该合同的状态会变成如下的样子,我们可以看到其中Status为Pending Tax, Bank, Contact。意思是联系方式、银行和税务信息没有填写。
1、填写联系方式
我们点击Contact Info下方的Set Up按钮可以进入联系方式填写页面,如下图:
如果你没有添加过联系人,你需要通过Add New Contact按钮来添加一个新的联系人。然后指定联系人的职务,职务如下:
- Senior Management:高管
- Financial:财务
- Technical:技术支持
- Legal:法务
- Marketing:市场推广
如果你是独立开发者,可以全部填你自己一个人。
2、填写银行卡信息
我们点击Bank Info下方的Set Up按钮可以进入联系方式填写页面,如下图:
选择你的银行账户,如果你没有,点击旁边的Add Bank Account添加一个账户。
下面是添加一个账户的流程。
2-1、选择银行所在的国家
2-2、填写银行CNAPS Code
如果你不知道CNAPS Code是多少,可以点击Look up Transit Number来查询,查询时会根据3个关键信息来查询,如下:
- Bank Name:银行的英文名称(不能是拼音)
- City:银行所在的城市英文名称(中国的城市用拼音)
- Postal Code:邮编
-
然后在下面就会出来备选的银行,选择正确的银行后,点击next,进入下一步。
2-3、确认银行信息
2-4、填写银行账号信息
- Bank Account Number:银行账号
- Confirm Bank Account Number:再次输入银行账号
- Account Holder Name:持卡人姓名,中文名用拼写,名在前,姓在后
-
Bank Account Currency:货币类型,一般国内的开发者选择CNY
2-5、确认所有信息
3、填写税务信息
税务信息这一块了解不是很多,不过因为是国内开发者,可以不用太费心,税务信息分3种:
- U.S Tax Forms: 美国税务
- Australia Tax Forms:澳大利亚税务
-
Canada Tax Forms: 加拿大税务
笔者选择的是U.S Tax Forms,选择后会问你两个问题,第一个问题如下:询问你是否是美国居民,有没有美国伙伴关系或者美国公司,如果没有直接选择No。
接下来第二个问题如下:询问你有没有在美国的商业性活动,没有也直接选No。
然后填写你的税务信息,包括以下几点:
- Individual or Organization Name:个人或者组织名称
- Country of incorporation: 所在国家
- Type of Beneficial Owner:受益方式,独立开发者选个人
- Permanent Residence:居住地址
- Mailing address:邮寄地址
- Name of Person Making this Declaration:声明人
- Title:头衔
填写完这些信息后就可以提交了
4、等待审核
当你填写完所有资料后,合同状态就会变成Processing,笔者凌晨1点左右提交,下午就通过了。
三、添加内购买项目
App创建好之后,我们打开创建的App,在左上角选择功能,会看到左侧的App 内购买项目。我们点击右下角的加号,为App添加内购项目。
虚拟物品添加分为如下几种:
- 消耗品(Consumable products):比如游戏内金币等。
- 不可消耗品(Non-consumable products):简单来说就是一次购买,终身可用(用户可随时从App Store restore)。
- 自动更新订阅品(Auto-renewablesubscriptions):和不可消耗品的不同点是有失效时间。比如一整年的付费周刊。在这种模式下,开发者定期投递内容,用户在订阅期内随时可以访问这些内容。订阅快要过期时,系统将自动更新订阅(如果用户同意)。
- 非自动更新订阅品(Non-renewablesubscriptions):一般使用场景是从用户从IAP购买后,购买信息存放在自己的开发者服务器上。失效日期/可用是由开发者服务器自行控制的,而非由AppStore控制,这一点与自动更新订阅品有差异。
- 免费订阅品(Free subscriptions):在Newsstand中放置免费订阅的一种方式。免费订阅永不过期。只能用于Newsstand-enabled apps。
类型2、3、5都是以Apple ID为粒度的。比如小张有三个iPad,有一个Apple ID购买了不可消耗品,则三个iPad上都可以使用。
类型1、4一般来说则是现买现用。如果开发者自己想做更多控制,一般选4
之后我们会看到类型的选项,如下图
官方的注释写的很清楚了,只在这里简单的说下前两种:
- 消耗型项目 就像你玩游戏需要买金币,买钻石等,只要花钱就可以无限次的购买
-
非消耗型项目 就像你在App Store购买App,买了一次之后就不用再买第二次,你拥有永久使用权。
在我们的app中,是充值会员,所以选择的是第一种,可以无限次购买。
这里有几个选项,需要填写商品名称,产品ID以及价格等级,简单说明一下
- 商品名称根据你的消费道具的实际意义来说明,比如“100颗宝石”,“100金币”等。
- 产品ID是比较重要的,由项目自定义,只要唯一即可,因为测试,我在这里随便填写的123,在实际应用中,一定要认真填写。
-
价格等级的话“查看价格表”中有对应的说明,可以对照着表中每个国家的货币价格与等级来选择
接下来是语言选择,和上传快照如下图
点击添加语言,填写名称和描述,这里我们依然选择简体中文,如下
审核备注,根据实际情况填写,可以不填。而下面的屏幕快照,则是商品图片,以像素为单位,最低尺寸为321,390,尺寸需求如下图,上传即可。
到这里为止, 我们的内购项目则添加完成。接下来则是测试阶段了。
四、申请沙盒测试账号(用来测试购买项目)
这个账号,是利用苹果的沙盒测试环境来模拟AppStore的购买流程,你肯定不会想要用真实RMB去购买测试吧?
首先我们回到iTunes Connect中,在这里我们选择用户和职能。
然后在上面的第三个选项沙箱技术测试员中点击加号,添加测试员。
在信息填写页面只简单说两句。
所有信息都可以随意填写,不用管是否真实。
App Store地区选择,一定要选对,它对应的是你创建的App的地区, 你App是中国的话, 在这里我们依然选择中国。
此账号只能用来测试,不要在正式的appstore上使用
填写完毕,点击保存后,我们则生成一个测试账号,当然这个账号是可以随时删除和添加的。
五、大致流程
IAP流程分为两种,一种是直接使用Apple的服务器进行购买和验证,另一种就是自己假设服务器进行验证。由于国内网络连接Apple服务器验证非常慢,而且也为了防止黑客伪造购买凭证,通用做法是自己架设服务器进行验证。
下面我们通过图来看看两种方式的差别:
5.1、使用Apple服务器
5.2、自己架设服务器
简单说下第二中情况的流程:
- 用户进入购买虚拟物品页面,App从后台服务器获取产品列表然后显示给用户
- 用户点击购买购买某一个虚拟物品,APP就发送该虚拟物品的productionIdentifier到Apple服务器
- Apple服务器根据APP发送过来的productionIdentifier返回相应的物品的信息(描述,价格等)
- 用户点击确认键购买该物品,购买请求发送到Apple服务器
- Apple服务器完成购买后,返回用户一个完成购买的凭证
- APP发送这个凭证到后台服务器验证
- 后台服务器把这个凭证发送到Apple验证,Apple返回一个字段给后台服务器表明该凭证是否有效
- 后台服务器把验证结果在发送到APP,APP根据验证结果做相应的处理
六、大致代码
#import "ViewController.h"
#import <StoreKit/StoreKit.h>
@interface ViewController () <SKPaymentTransactionObserver, SKProductsRequestDelegate>
@property (nonatomic, strong) NSString * receipt;
@end
@implementation ViewController
/**
* 开发流程:
* 1、引入头文件 #import <StoreKit/StoreKit.h>
* 2、检查用户是否允许使用 内置购买 [SKPaymentQueue canMakePayments]
* 3、如果允许:获取所有付费Product ID(productionIdentifier)列表。可以用常量保存在本地,也可以自己服务器返回(原因:连接苹果服务器比较慢)
* 4、先查询是否有该productionIdentifier的商品信息,获取SKPayment实例,然后通过SKPaymentQueue的 addPayment方法发起一个购买的操作。
* 5、监听购买结果 [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
* 6、购买完成回调- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
* 7、服务器验证凭证(Optional)。如果购买成功,我们需要将凭证发送到服务器上进行验证。
* 服务器工作:
* 7.1 接收ios端发过来的购买凭证。
* 7.2 判断凭证是否已经存在或验证过,然后存储该凭证。
* 7.3 将该凭证发送到苹果的服务器验证,并将验证结果返回给客户端。
* 7.4 如果需要,修改用户相应的会员权限。
* 8、网络异常、崩溃等导致无法验证,将购买凭证持久化。用户下次启动的时候,再发一次验证
* 8.1 保存数据(日期、用户、凭证receipt)
* 8.2 重新启动,读取数据,发送网络请求
* 8.3 如果成功,删除文件
*/
- (void)viewDidLoad {
[super viewDidLoad];
// 监听购买结果
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
}
- (void)buyProdutin:(UIButton *)sender
{
// 确认用户是否允许使用 内置购买
if([SKPaymentQueue canMakePayments]){
// 获取商品的productionIdentifier
NSString * productIdentifier = @""; //用户选择
[self getProductInfo:productIdentifier];
}else{
}
}
#pragma mark - 查询productIdentifier 的商品信息
- (void)getProductInfo:(NSString *)productIdentifier
{
NSArray * product = [[NSArray alloc] initWithObjects:productIdentifier, nil];
NSSet * set = [NSSet setWithArray:product];
// 请求苹果服务器,是否有该商品
SKProductsRequest * request = [[SKProductsRequest alloc] initWithProductIdentifiers:set];
request.delegate = self;
[request start];
// 这里可弹出提示信息 : 正在购买,请稍后
}
// 查询该产品信息
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
{
NSArray * myProduct = response.products;
if(myProduct.count == 0){
// 说明没有该商品,或查询失败
return;
}
// 如果有,发起购买操作
SKPayment * payment = [SKPayment paymentWithProduct:myProduct[0]];
[[SKPaymentQueue defaultQueue] addPayment:payment];
}
// 查询失败
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error
{
// 提示信息
}
#pragma mark - 购买回调
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions
{
for(SKPaymentTransaction * transaction in transactions){
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchased:
// 交易成功
[self completeTransaction:transaction];
break;
case SKPaymentTransactionStateFailed:
// 交易失败
[self failedTransaction:transaction];
break;
case SKPaymentTransactionStateRestored:
// 已经购买过该商品
[self restoreTransaction:transaction];
break;
case SKPaymentTransactionStatePurchasing:
// 商品添加进列表
break;
default:
break;
}
}
}
//沙盒测试环境验证
#define SANDBOX @"https://sandbox.itunes.apple.com/verifyReceipt"
//正式环境验证
#define AppStore @"https://buy.itunes.apple.com/verifyReceipt"
/**
* 验证购买,避免越狱软件模拟苹果请求达到非法购买问题
// 交易结束后,验证票据信息是否正确,之后才可以给用户商品
- (void)completeTransaction:(SKPaymentTransaction *)transaction {
// 测试环境
self.receipt = [[NSData dataWithContentsOfURL:[[NSBundle mainBundle] appStoreReceiptURL]] base64EncodedStringWithOptions:0];
NSError *error;
NSDictionary *requestContents = @{
@"receipt-data": self.receipt
};
NSData *requestData = [NSJSONSerialization dataWithJSONObject:requestContents
options:0
error:&error];
// 设置URL
NSURL *storeURL = [NSURL URLWithString:@"https://sandbox.itunes.apple.com/verifyReceipt"];
NSMutableURLRequest *storeRequest = [NSMutableURLRequest requestWithURL:storeURL];
[storeRequest setHTTPMethod:@"POST"];
[storeRequest setHTTPBody:requestData];
// 网络请求,验证
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}
// 交易失败后,回调
- (void)failedTransaction:(SKPaymentTransaction *)transaction {
if(transaction.error.code != SKErrorPaymentCancelled) {
// 购买失败
} else {
// 用户取消交易
}
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}
// 已购买过商品回调
- (void)restoreTransaction:(SKPaymentTransaction *)transaction {
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}
#pragma mark - 发送凭证失败的处理
/**
* 1 保存数据(日期、用户、凭证receipt)
* 2 重新启动,读取数据,发送网络请求
* 3 如果成功,删除文件
*/
- (void)dealloc
{
[[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
}
@end
需要注意以下几点:
- 代码中的self.profuctIdArr所填写的是你的购买项目的的ID,我这里是当时填写的ID 123。
- 在监听购买结果后,一定要调用[[SKPaymentQueue defaultQueue] finishTransaction:tran];来允许你从支付队列中移除交易。
- 沙盒环境测试appStore内购流程的时候,请使用没越狱的设备。
- 请务必使用真机来测试,一切以真机为准。
- 项目的Bundle identifier需要与您申请AppID时填写的bundleID一致,不然会无法请求到商品信息。
- 真机测试的时候,一定要退出原来的账号,才能用沙盒测试账号
- 二次验证,请注意区分宏, 测试用沙盒验证