前言:什么时候用内购?---平台有虚拟货币交易而非真实货品交易且有充值的时候,打比方题主项目是在平台充值优币(相当于金币),然后用该货币去购买资源等作用,并非一种真实的物品交易。在Appstore市场不选择内购则有可能面临下架风险。
一、签订内购协议:
1、签订前的准备
(1)联系人信息(可以只填一人,这人有一定代表性即可,我全部填写的我们总监,省事):职称和邮箱、电话;
(2)公司账户信息:公司结算账户账号、开户行行号(实在不知道的问清楚开户行地址也行,后面具体介绍根据地址找到行号)、持卡人姓名(名在前姓在后,我们是公司账号名,就得要公司的英文账户名)。
2、开始签订:登录ITunes Connect点击左上角进入:
点击协议进入:
3、填写联系人信息:
没签订之前contact Info 下按钮显示的是setup,点击进入:
我这里为了省事高管、财务、技术人员、法人、市场全部都可以填写一个具有代表性的人;
4、添加银行业务:
银行国家我们填写中国:
填写code银行开户行行号,不知道行号的可以根据银行名字、城市和邮编搜索出一系列的支行行号,再根据开户银行地址选择所对应的行号;
CNAPS Code:就是开户行行号;
5、填写税务信息:
点击第一个就行;
进入U.S Tax后,会有两个选项,是否是美国人或者亲属是美国人,根据实际情况填写Yes/No,还有一个是否参与美国活动,也是一样的填写Yes/No,一般都是NO,根据实际情况来吧;
中间一截都不用管,最后勾选同意就行的,当然谨慎者有研究精神者多去看看是很好的;
协议签订完了,可是完成了一件大事!
二、创建内购项目并提交:
进入内购项目,点击➕即添加内购项目;
我们是购买金币类型,所以是消耗型项目;
创建产品的时候,产品ID是唯一的,最好定义bundleId+产品标识;
创建内购产品审核备注那里填写创建的沙盒测试账号以供上线审核:
沙盒测试账号密码不可修改,忘记了可以重新创建一个即可,点击编辑可删除该账号:
三、代码:
去苹果服务器请求:
#pragma mark - Appstore Request
- (void)requestProductData:(NSString *)type {/**<去苹果服务器请求商品*/
NSLog(@"-------------请求对应的产品信息----------------");
[[WBProgressHUDView sharedInstance] showInView:self.view withTitleLabelText:@"请稍后..." withUserInterfaceEnable:YES];
NSArray *product = [[NSArray alloc] initWithObjects:type,nil];
NSSet *set = [NSSet setWithArray:product];
SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:set];
request.delegate = self;
[request start];
}
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {/**<收到产品返回信息*/
NSLog(@"--------------收到产品反馈消息---------------------");
NSArray *product = response.products;
if([product count] == 0){
[[WBProgressHUDView sharedInstance] dismissFromSuperView];
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:self.productId]){
p = pro;
}
}
SKPayment *payment = [SKPayment paymentWithProduct:p];
NSLog(@"发送购买请求");
[[SKPaymentQueue defaultQueue] addPayment:payment];
}
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error {/**<请求失败*/
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"温馨提示" message:@"支付失败!" delegate:nil cancelButtonTitle:@"确定" otherButtonTitles:nil, nil];
[alertView show];
NSLog(@"------------------错误-----------------:%@", error);
}
- (void)requestDidFinish:(SKRequest *)request {/**<请求结束*/
[[WBProgressHUDView sharedInstance] dismissFromSuperView];
NSLog(@"------------反馈信息结束-----------------");
}
从苹果沙盒环境验证购买:(适用于本地测试,真实环境请切换至服务器验证,** 测试的时候一定要记得是沙盒环境!!! **)
#pragma mark - Verify in Environment
/**<沙盒测试环境验证*/
#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:@"com.k12chn.UCoin6"] || [productIdentifier isEqualToString:@"com.k12chn.UCoin80"] || [productIdentifier isEqualToString:@"com.k12chn.UCoin120"]) {
NSInteger purchasedCount = [defaults integerForKey:productIdentifier];/**<已购买数量*/
[[NSUserDefaults standardUserDefaults] setInteger:(purchasedCount+1) forKey:productIdentifier];
} else {
[defaults setBool:YES forKey:productIdentifier];
}
/**<在此处对购买记录进行存储,可以存储到开发商的服务器端*/
} else {
NSLog(@"购买失败,未通过验证!");
}
}
题主公司项目服务器验证购买请求参数:
NSURL *receiptUrl = [[NSBundle mainBundle] appStoreReceiptURL];
NSData *receiptData = [NSData dataWithContentsOfURL:receiptUrl];
NSString *receiptString = [receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];//转化为base64字符串
NSString *url = @"/index.php?app=account_epay&act=UcoinExcharge";
NSDictionary *dic = @{
@"userId":userInfoModel.UserId,
@"receiptdata":receiptString,
@"ucoin":ucoin,
@"money":price
};
购买结果监听:
#pragma mark - Observe Result
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transaction{
for(SKPaymentTransaction *tran in transaction) {/**<监听购买结果*/
switch (tran.transactionState) {
case SKPaymentTransactionStatePurchased: {
NSLog(@"交易完成");
/**<发送到苹果服务器验证凭证*/
[self requstVerifyPurchase];
[[SKPaymentQueue defaultQueue] finishTransaction:tran];
}
break;
case SKPaymentTransactionStatePurchasing:
NSLog(@"商品添加进列表");
break;
case SKPaymentTransactionStateRestored: {
NSLog(@"已经购买过商品");
[[SKPaymentQueue defaultQueue] finishTransaction:tran];
}
break;
case SKPaymentTransactionStateFailed: {
NSLog(@"交易失败");
[[SKPaymentQueue defaultQueue] finishTransaction:tran];
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"温馨提示" message:@"购买失败!" delegate:nil cancelButtonTitle:@"确定" otherButtonTitles:nil, nil];
[alertView show];
}
break;
default:
break;
}
}
}
- (void)completeTransaction:(SKPaymentTransaction *)transaction {/**<交易结束*/
NSLog(@"交易结束");
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
- (void)dealloc {
[[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
}
OK啦,成功告一段落了,但是最后要注意的是:
测试的时候需要先进设置->iTunes Store 与 App Store 里面退出原来真实的Apple ID(退出就好,无需急着登录测试账号),然后在app中测试购买的时候回跳出请输入Appstore账号,这时输入我们的沙盒测试账号即可。
最后来处理掉单问题了:
两种情况:1、付款了Appstore服务器出问题回调不成功;
2、AppStore返会成功但自己服务器验证未通过未发放对应货币。
针对情况1:我在AppDelegate里重新监听向AppStore购买请求结果,如果存在订单但又回执失败的话,是会重新发送请求的;
针对情况2:我在购买监听回调中做了个3次循环验证,如果超过3次还失败,那也无需继续请求,说明服务器出问题了。
(ps:总觉得这些处理内心不是很踏实不是很满意,求有其它好处理方式的共享,感谢~)
内购除了开发过程坑,还有收账过程也是有点意思,因为是海外入账,银行会打电话给公司,需提供形式发票等一系列资料才能完整到公司银行账户。财务一开始没有及时收账,导致银行又原路退回到苹果公司。后面天天逼着我一个小小开发者做发票,也是赶鸭子上架,各种百度,尝试性地问财务要一个平时的发票格式,我依葫芦画瓢把一些内购买数据和账单里一些数据填上去,结果神奇般地后面的款项都收到了,于是又回头去联系苹果财务,请求将先前的退款重新发起,我们及时提供形式发票等资料就可以入账。那真的是一波三折~知道这个过程的,还是很easy。不知道的,度娘上资料都微乎其微!