iOS内购开发前信息注册、代码开发过程、以及最后收款

前言:什么时候用内购?---平台有虚拟货币交易而非真实货品交易且有充值的时候,打比方题主项目是在平台充值优币(相当于金币),然后用该货币去购买资源等作用,并非一种真实的物品交易。在Appstore市场不选择内购则有可能面临下架风险。


一、签订内购协议:

1、签订前的准备

(1)联系人信息(可以只填一人,这人有一定代表性即可,我全部填写的我们总监,省事):职称和邮箱、电话;

(2)公司账户信息:公司结算账户账号、开户行行号(实在不知道的问清楚开户行地址也行,后面具体介绍根据地址找到行号)、持卡人姓名(名在前姓在后,我们是公司账号名,就得要公司的英文账户名)。

2、开始签订:登录ITunes Connect点击左上角进入:


签订协议税务银行业务.png

点击协议进入:


进入协议.png

3、填写联系人信息:

没签订之前contact Info 下按钮显示的是setup,点击进入:

我这里为了省事高管、财务、技术人员、法人、市场全部都可以填写一个具有代表性的人;

选择联系人png
添加联系人.png

4、添加银行业务:

银行国家我们填写中国:


银行所在国家.png

填写code银行开户行行号,不知道行号的可以根据银行名字、城市和邮编搜索出一系列的支行行号,再根据开户银行地址选择所对应的行号;

CNAPS Code:就是开户行行号;


查找开户行行号.png
填写银行账户信息.png

5、填写税务信息:

点击第一个就行;

选择税务信息.png

进入U.S Tax后,会有两个选项,是否是美国人或者亲属是美国人,根据实际情况填写Yes/No,还有一个是否参与美国活动,也是一样的填写Yes/No,一般都是NO,根据实际情况来吧;

类型.png

中间一截都不用管,最后勾选同意就行的,当然谨慎者有研究精神者多去看看是很好的;

勾选同意税务png

协议签订完了,可是完成了一件大事!


二、创建内购项目并提交:

进入内购项目,点击➕即添加内购项目;

内购项目.png

我们是购买金币类型,所以是消耗型项目;


消耗型项目.png

创建产品的时候,产品ID是唯一的,最好定义bundleId+产品标识;

创建内购产品-名称定价png

创建内购产品审核备注那里填写创建的沙盒测试账号以供上线审核:

创建内购产品-审核备注.png

沙盒测试账号密码不可修改,忘记了可以重新创建一个即可,点击编辑可删除该账号:


沙盒测试账号.png

三、代码:
去苹果服务器请求:

#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。不知道的,度娘上资料都微乎其微!


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

推荐阅读更多精彩内容