最近项目中要加进去虚拟币和会员的功能 本以为特别简单不就是几十行代码嘛 但是内购做下来 可把我坑惨了 有什么问题尽可留言 一定帮你们顺利通过内购
文章末尾有内购遇见的bug 以及解决办法
一 首先区分一下 Apple Pay 和内购完全是两码事
本来说集成内购的 但我们后台大哥说可以使用apple pay 也是苹果的支付系统 没做过内购我以为就是一样的 然后和我们超哥 三下五除二的把applepay集成进来的 然后也是线下测试 线上测试 一大堆 终于好了 开始提测 果然 因为applepay悲剧了 呵呵呵呵了
二 开始集成内购
1、先到iTunes Connect上填写协议、税务和银行业务
如果你是外包公司,那么你可以让你的客户填写这一堆信息;如果你只是是产品公司的技术开发人员,那么你可以让项目负责人填写这一堆信息;如果没有如果,兄弟辛苦了,自己动手来吧。
Paste_Image.png Paste_Image.png
先点击Contact Info 的Set Up
Paste_Image.png Paste_Image.png Paste_Image.png Paste_Image.png Paste_Image.png Paste_Image.png Paste_Image.png进行十二步的时候可能有些银行通过下面的Look up CNAPS Code方法查不到,就需要借助百度了,一定要准确查询,否则会有问题。推荐一个地址
https://e.czbank.com/CORPORBANK/query_unionBank_index.jsp
这一步需要注意的是,货币类型可能有歧义,看你是想收美元还是人民币了,都说美元合适。不过,我做的时候为了避免事情,还是选择了CNY,支持国产。还有一点,银行账号如果是对公的账号,需要填写公司的英文名称,如果没有的话,上拼音!然后点击保存银行信息就算ok了,然后退回到最开始的页面
Paste_Image.png Paste_Image.png Paste_Image.png Paste_Image.png Paste_Image.png Paste_Image.png Paste_Image.png Paste_Image.png2 为app添加内购产品
在iTunes Connect在你要添加内购的app中,进入到功能页面
Paste_Image.png在你点击添加内购产品按钮后会有弹框,提示你选择类型,这个就要看你app的需求了
Paste_Image.png Paste_Image.png Paste_Image.png Paste_Image.png填写完审核信息后,点击右上角的“存储”按钮,就添加了一个内购产品~
3添加沙盒技术测试员
在iTunes Connect的用户和智能中选择“沙盒技术测试员”,填写信息保存以后就有一个测试员了
Paste_Image.png直接鲁代码
#import <StoreKit/StoreKit.h>
@interface LLMIneCoinRechargeController ()<UIAlertViewDelegate,SKProductsRequestDelegate,SKPaymentTransactionObserver>
@property(nonatomic, strong)NSArray<LLMIneCoinRechargeModel *> *coinModelArr;
@property(nonatomic,copy)NSString * bvip;
@property(nonatomic,copy)NSString * coin;
@property(nonatomic, strong)SKProductsRequest *request;
@property(nonatomic,copy)NSString * productID;
@implementation LLMIneCoinRechargeController{
UIAlertView* _alertView;
NSString *coinStr;
}
- (void)viewDidLoad {
[super viewDidLoad];
//一定要 开启内购检测
[[SKPaymentQueue defaultQueue]addTransactionObserver:self];
}
// 在点击事件中去添加代码
- (void)payBtnClick:(UIButton *)sender{
__block LLMIneCoinRechargeModel *model = nil;
[self.coinModelArr enumerateObjectsUsingBlock:^(LLMIneCoinRechargeModel * obj, NSUInteger idx, BOOL * _Nonnull stop) {
if (obj.selected) {
model = obj;
}
}];
if (!model) {
[HudManager showMessage:@"请选择金额"];
return;
}
NSString *proID = [NSString stringWithFormat:@"charge%@",model.coinID];
WS(weakSelf);
if ([SKPaymentQueue canMakePayments]) {
[weakSelf requestProductData:proID];
weakSelf.productID = proID;
}else{
[HudManager showMessage:@"不允许程序内付费"];
}
}
// 收到请求信息
- (void)requestProductData:(NSString *)productID{
NSLog(@"-------------请求对应的产品信息----------------");
[SVProgressHUD showWithStatus:nil maskType:SVProgressHUDMaskTypeBlack];
NSArray *product = [[NSArray alloc] initWithObjects:productID,nil];
NSSet *nsset = [NSSet setWithArray:product];
_request = [[SKProductsRequest alloc]initWithProductIdentifiers:nsset];
_request.delegate = self;
[_request start];
}
// 收到返回信息
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{
NSArray *product = response.products;
if (product.count == 0) {
[SVProgressHUD dismiss];
[HudManager showMessage:@"购买失败"];
return;
}
SKProduct *prod = nil;
for (SKProduct *pro in product) {
if ([pro.productIdentifier isEqualToString:self.productID]) {
prod = pro;
}
}
// 发送购买请求
if (prod != nil) {
SKPayment *payment = [SKPayment paymentWithProduct:prod];
[[SKPaymentQueue defaultQueue]addPayment:payment];
}
}
// 失败回调
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error{
[SVProgressHUD dismiss];
[HudManager showMessage:@"购买失败"];
}
// 支付后的反馈信息
- (void)requestDidFinish:(SKRequest *)request{
[SVProgressHUD dismiss];
}
// 监听购买结果
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions{
for (SKPaymentTransaction *tran in transactions) {
switch (tran.transactionState) {
case SKPaymentTransactionStatePurchased:
[self verifyPurchaseWithPaymentTransactionWith:tran];
break;
case SKPaymentTransactionStatePurchasing:
NSLog(@"商品已经添加进列表");
break;
case SKPaymentTransactionStateRestored:
NSLog(@"已经购买过商品");
[[SKPaymentQueue defaultQueue] finishTransaction:tran];
break;
case SKPaymentTransactionStateFailed:
NSLog(@"购买失败");
[[SKPaymentQueue defaultQueue] finishTransaction:tran];
[HudManager showMessage:@"购买失败"];
break;
default:
break;
}
}
}
/**
* 验证购买,避免越狱软件模拟苹果请求达到非法购买问题
*
*/
-(void)verifyPurchaseWithPaymentTransactionWith:(SKPaymentTransaction *)tran{
//从沙盒中获取交易凭证并且拼接成请求体数据
NSURL *receiptUrl=[[NSBundle mainBundle] appStoreReceiptURL];
NSData *receiptData=[NSData dataWithContentsOfURL:receiptUrl];
NSString *receiptString = [receiptData base64EncodedStringWithOptions:0];
if (!receiptString) {
return;
}
// 将base64 编码 发给后台服务器 做验证 以及是否是沙箱环境的参数
NSDictionary *dict = [NSDictionary universalParameteWithDictionary:@{@"receipt":receiptString,
@"sandbox":BOOLSandBox,
}];
[[LFBHTTPSessionManager manager]POSTwithURLString:KApplePay_Docheck parameters:dict success:^(id data) {
[self loadData];
[[SKPaymentQueue defaultQueue] finishTransaction:tran];
} failed:^(NSError *error) {
// [HudManager showMessage:error.description];
}];
}
- (void)dealloc{
[[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
}
代码基本上就上边的了
四 、要注意的事项!
1.bundleID要与iTunes Connect上你App的相同,不然是请求不到产品信息的
2.在沙盒环境进行测试内购的时候,要使用没有越狱的苹果手机。
3.在沙盒环境下真机测试内购时,请去app store中注销你的apple ID,不然发起支付购买请求后会直接case:SKPaymentTransactionStateFailed。使用沙盒测试员的账号时不需要真正花钱的。
4.如果只添加了一个沙盒测试员账号,当一个真机已经使用了这个账号,另一个真机再使用这个账号支付也是会发生错误的。那就去多建几个沙盒测试员账号使用不同的,反正也是免费的,填写也很快。
5.监听购买结果,当失败和成功时代码中要调用:
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
该方法通知苹果支付队列该交易已完成,不然就会已发起相同 ID 的商品购买就会有此项目将免费恢复的提示。
1
uideline 3.1.1 - Business
We noticed that your app enables the purchase of content, services, or functionality in the app by means other than the in-app purchase API, which is not appropriate for the App Store.
解决办法 这个被拒的原因很简单了 是要要使用内购了
2 切记 内购和Apple Pay 是两回事
3
We noticed that your in-app purchase product is set to an incorrect product type.
VIP is set to consumable.
Next Steps
Since the service offered by your app requires the user to make an advance payment to access the content or receive the service, please use the non-renewable subscription in-app purchase product type. Non-renewable subscription content must be made available to all iOS devices owned by a single user, as indicated in guideline 3.1.2 of the App Store Review Guidelines.
Note: The product type cannot be changed once an in-app purchase product has been created. Therefore, you will need to create a new in-app purchase product with the correct product type.
解决办法 : 虚拟币是消耗类型 但是一定要记住 会员VIP不是消耗类型的的 是非订阅类型的 更改你的VIP类型
4
Guideline 3.1.2 - Business
Your app offers a content subscription but does not have a mechanism in place to support the requirement that the subscription content be available to the user on all of their iOS devices.
Next Steps
To resolve this issue, please modify your app to include an optional user registration feature to deliver subscription content to all of the user's iOS devices. Such user registration must be made optional, not required. We also recommend indicating that registering is required to access the subscription content from their other iOS devices - and providing a way to register later, if users wish to have access to this content at a future time.
这个被拒的原因就感觉比较奇葩了 不知道是不是因为vip是费续期订阅的问题 因为我的项目中有游客入口 也就是不用登陆 也可以进去看到部分内容以及使用部分功能 : 所以应该是这个游客入口 和内购的一些规则冲突了
有两种解决办法:
1 就是游客模式 不能出现购买虚拟币以及购买VIP的页面 (我是这么改的 然后重新提交审核 就通过了)
2 有朋友是这么做的 也通过审核了 但个人感觉相对麻烦一点
就是 游客模式可以出现购买虚拟币以及购买VIP的页面 而且允许游客购买 购买后的凭证要保存在本地 然后当用户登录的时候 把这个凭证在发给服务器去验证 验证成功后 将虚拟币或者是VIp加到当前账户上
网友评论
楼主,你最近按这种方式更新含有内购的app了吗?这种方式现在会不会被拒
Address line 1 cannot be more than 40 characters
这个是什么意思
Address Line 1 这一行 这不报超过40个字符了 而且这一行里填写的地址 也是自动生成的 应该和我那个申请证书地址一样 我现在把这一行的东西 分成两部分 在Address Line 1这一行里填 城市名 在Address Line 2这一行里填具体的街道 是否可行
比如99$的个人账号是shunzi Wang那么这个对公账户的开户名 也得是这个shunzi Wang