iOS之内购

时间:2023-02-01 10:58:49

很久之前就想出一篇IOS内付费的教程,但是一查网上的教程实在太多了,有的写得真的蛮不错的,就心想算了,于是就保存在草稿箱了。至于为什么写完它呢!真是说来话长,最近公司有个项目经理跑来问我有关苹果内付费相关的细节,跟他聊了半天,从项目对接苹果官方支付接口聊到了如何查看App收益,最后终于使他有了一些眉目,但是悲催的是还要我继续去跟他们项目的程序员讲解(真是疯了),所以我就决定给他们项目写一个内购的文档,所以我顺便把这篇博客完成吧!

首先进入苹果的ItunesConnection(https://itunesconnect.apple.com)点击左上角的加号新建一个App应用,点击后该网站会弹出一个信息编辑框,大家只要将上面的信息填充完毕点击save即可在苹果的app平台上拥有一个属于自己的App。

iOS之内购

在套装ID的上,需要提前为该App申请一个AppID以及BundleID,只要是申请成功了就会在选择列表中显示出来。如果有人有疑问如何申请,请看我之前那一篇推送的博客,里面有详细的步骤。附上链接点击打开链接

这里顺便多说一句这个ItunesConnect是用来干嘛的,它是苹果公司给个人或企业提供管理自己App的一个平台。在这个平台上开发者可以新建,删除和管理自己的App应用,开发者可以根据需求对App应用进行上架与下架,编辑App信息,生成测试app所需的信息,例如账号,邀请码等,还有就是我们今天要讲的内付费功能。当然啦,他的功能可不止我讲的这些,我大致说一下这个平台的作用,如果你经常跟它打交道的话就会慢慢熟悉了。

接下来,我就来为大家演示一下如何添加付费道具,首先打开iTunesConnect,显示如下页面:

iOS之内购

选择红圈所圈起来的选项,然后将里面的相关信息补充完毕,如果缺少这一步,内购功能是不会成功的。

假如你已经完成了上述相关银行账户的设置,就点击你的App,选择上面标题栏中的"App 内购买项目"

iOS之内购

随后点击左上角的 "create new"选项,如下图所示,进入到下一个界面:

iOS之内购

这个界面是让你选择消费道具的种类,现在改版的网站是有简体中文翻译的,所以不像以前打开一看都不知道选哪一个,甚至都不知道每个代表的什么意思(比如我第一次遇到的时候,在领导面前真是囧)。它的种类分为如下几种:

iOS之内购

一般对项目来说大多数都是选择“消耗型项目”这个种类,比如游戏中购买金币,宝石balabala~之类的,选中之后就会到这个界面中来:

iOS之内购

在上图所示的编辑框中输入,商品名称,产品ID以及价格等级,在这边说明一下:

1.商品名称根据你的消费道具的实际意义来说明,比如“100颗宝石”,“100金币”等。

2.产品ID是比较重要的,由项目自定义,只要唯一即可,像我一般都是用App的bundleID加一个后缀来表示,这样既跟项目关联又具有唯一性。

3.价格等级的话“查看价格表”中有对应的说明,可以对照着表中每个国家的货币价格与等级来选择。

我们继续,在这个网页的接下来部分如图所示:

iOS之内购

选择添加语言选项,弹出一个编辑页面:

iOS之内购

点击save保存,则会在界面上显示成如下:

iOS之内购

最后一步就是点击“选取文件”提交一张苹果它指定像素(640*920)的商品图片,当他上传完毕后点击“save”按钮,我们这第二部分就大工告成了。提交的商品最后会在内购的页面上显示为如图:

iOS之内购

这个图是我在已经发布的app上面截取的,添加了3个商品,已经是通过的的状态了(显示绿色),当您刚提交的时候,因为通过苹果的审查需要一段时间所以会显示黄色的等待状态,所以不必担心是不是商品编辑错了。如图:

iOS之内购

这部分,我主要给大家演示一下,如何申请测试账号,利用苹果的沙盒测试环境来模拟AppStore的购买流程。

在ItunesConnect中选择“用户和职能”选项~

iOS之内购

随后在左上角的选项中选择沙盒测试者,点击左上角的加号图标增加一位测试者,如图:

iOS之内购

编辑好相应的内容,点击保存,就创建了一个测试账号,是不是很简单啊!当然这个账号如果你忘记了密码可以重新生成一个,无关紧要。

顺带多句嘴,不要在正式的appstore上面用沙盒测试的账号来登录,千万要牢记在心,此账号只用于测试环境下~

接下来就是代码部分啦~

1.首先在项目工程中加入“storekit.framework”,加入头文件#import <StoreKit/StoreKit.h>

2.在.h文件中加入“SKPaymentTransactionObserver,SKProductsRequestDelegate”监听机制

下面贴上内购的核心代码,就几个函数,我在这边就不在做更多详细的解释了,各位看官可以运行跑一下就一目了然了。

.h文件

  1. //
  2. //  PaymentViewController.h
  3. //  IAPPayTest
  4. //
  5. //  Created by silicon on 14-10-28.
  6. //  Copyright (c) 2014年 silicon. All rights reserved.
  7. //
  8. #import <UIKit/UIKit.h>
  9. #import <StoreKit/StoreKit.h>
  10. @interface PaymentViewController : UIViewController<SKPaymentTransactionObserver,SKProductsRequestDelegate>
  11. @property (strong, nonatomic) IBOutlet UITextField *productID;
  12. @property (strong, nonatomic) IBOutlet UIButton *purchase;
  13. - (IBAction)purchaseFunc:(id)sender;
  14. @end

.m文件

  1. //
  2. //  PaymentViewController.m
  3. //  IAPPayTest
  4. //
  5. //  Created by silicon on 14-10-28.
  6. //  Copyright (c) 2014年 silicon. All rights reserved.
  7. //
  8. #import "PaymentViewController.h"
  9. @interface PaymentViewController ()
  10. @end
  11. @implementation PaymentViewController
  12. - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
  13. {
  14. self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
  15. if (self) {
  16. // Custom initialization
  17. }
  18. return self;
  19. }
  20. - (void)viewDidLoad
  21. {
  22. [super viewDidLoad];
  23. // Do any additional setup after loading the view from its nib.
  24. [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
  25. self.productID.text = @"com.games.ztyxs.product_point.1";
  26. }
  27. - (void)didReceiveMemoryWarning
  28. {
  29. [super didReceiveMemoryWarning];
  30. // Dispose of any resources that can be recreated.
  31. }
  32. - (IBAction)purchaseFunc:(id)sender {
  33. NSString *product = self.productID.text;
  34. if([SKPaymentQueue canMakePayments]){
  35. [self requestProductData:product];
  36. }else{
  37. NSLog(@"不允许程序内付费");
  38. }
  39. }
  40. //请求商品
  41. - (void)requestProductData:(NSString *)type{
  42. NSLog(@"-------------请求对应的产品信息----------------");
  43. NSArray *product = [[NSArray alloc] initWithObjects:type, nil nil];
  44. NSSet *nsset = [NSSet setWithArray:product];
  45. SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:nsset];
  46. request.delegate = self;
  47. [request start];
  48. }
  49. //收到产品返回信息
  50. - (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{
  51. NSLog(@"--------------收到产品反馈消息---------------------");
  52. NSArray *product = response.products;
  53. if([product count] == 0){
  54. NSLog(@"--------------没有商品------------------");
  55. return;
  56. }
  57. NSLog(@"productID:%@", response.invalidProductIdentifiers);
  58. NSLog(@"产品付费数量:%d",[product count]);
  59. SKProduct *p = nil;
  60. for (SKProduct *pro in product) {
  61. NSLog(@"%@", [pro description]);
  62. NSLog(@"%@", [pro localizedTitle]);
  63. NSLog(@"%@", [pro localizedDescription]);
  64. NSLog(@"%@", [pro price]);
  65. NSLog(@"%@", [pro productIdentifier]);
  66. if([pro.productIdentifier isEqualToString:self.productID.text]){
  67. p = pro;
  68. }
  69. }
  70. SKPayment *payment = [SKPayment paymentWithProduct:p];
  71. NSLog(@"发送购买请求");
  72. [[SKPaymentQueue defaultQueue] addPayment:payment];
  73. }
  74. //请求失败
  75. - (void)request:(SKRequest *)request didFailWithError:(NSError *)error{
  76. NSLog(@"------------------错误-----------------:%@", error);
  77. }
  78. - (void)requestDidFinish:(SKRequest *)request{
  79. NSLog(@"------------反馈信息结束-----------------");
  80. }
  81. //监听购买结果
  82. - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transaction{
  83. for(SKPaymentTransaction *tran in transaction){
  84. switch (tran.transactionState) {
  85. case SKPaymentTransactionStatePurchased:
  86. NSLog(@"交易完成");
  87. break;
  88. case SKPaymentTransactionStatePurchasing:
  89. NSLog(@"商品添加进列表");
  90. break;
  91. case SKPaymentTransactionStateRestored:
  92. NSLog(@"已经购买过商品");
  93. break;
  94. case SKPaymentTransactionStateFailed:
  95. NSLog(@"交易失败");
  96. break;
  97. default:
  98. break;
  99. }
  100. }
  101. }
  102. //交易结束
  103. - (void)completeTransaction:(SKPaymentTransaction *)transaction{
  104. NSLog(@"交易结束");
  105. [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
  106. }
  107. - (void)dealloc{
  108. [[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
  109. [super dealloc];
  110. }
  111. @end

代码就这么多,到这边我们的IOS内购教程就接近尾声了,在测试的时候还有几点因素要注意一下:

1.沙盒环境测试appStore内购流程的时候,请使用没越狱的设备。

2.请务必使用真机来测试,一切以真机为准。

3.项目的Bundle identifier需要与您申请AppID时填写的bundleID一致,不然会无法请求到商品信息。

讲了这么多,附上几张测试截屏给大家展示一下:

请求商品时的打印日志:

iOS之内购

交易成功后:

iOS之内购

手机截屏:

要求输入AppStore帐密,使用测试生成的即可:

iOS之内购

确定购买:

iOS之内购

交易完成:

iOS之内购

当我们的交易完成后还要去appstore 上面去验证票据信息是否正确,这样我们才可以给玩家发放道具,apple官方文档

  1. //交易结束
  2. - (void)completeTransaction:(SKPaymentTransaction *)transaction{
  3. NSLog(@"交易结束");
  4. //交易验证
  5. NSURL *recepitURL = [[NSBundle mainBundle] appStoreReceiptURL];
  6. NSData *receipt = [NSData dataWithContentsOfURL:recepitURL];
  7. if(!receipt){
  8. }
  9. NSError *error;
  10. NSDictionary *requestContents = @{
  11. @"receipt-data": [receipt base64EncodedStringWithOptions:0]
  12. };
  13. NSData *requestData = [NSJSONSerialization dataWithJSONObject:requestContents
  14. options:0
  15. error:&error];
  16. if (!requestData) { /* ... Handle error ... */ }
  17. //In the test environment, use https://sandbox.itunes.apple.com/verifyReceipt
  18. //In the real environment, use https://buy.itunes.apple.com/verifyReceipt
  19. // Create a POST request with the receipt data.
  20. NSURL *storeURL = [NSURL URLWithString:@"https://buy.itunes.apple.com/verifyReceipt"];
  21. NSMutableURLRequest *storeRequest = [NSMutableURLRequest requestWithURL:storeURL];
  22. [storeRequest setHTTPMethod:@"POST"];
  23. [storeRequest setHTTPBody:requestData];
  24. // Make a connection to the iTunes Store on a background queue.
  25. NSOperationQueue *queue = [[NSOperationQueue alloc] init];
  26. [NSURLConnection sendAsynchronousRequest:storeRequest queue:queue
  27. completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
  28. if (connectionError) {
  29. /* ... Handle error ... */
  30. } else {
  31. NSError *error;
  32. NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
  33. if (!jsonResponse) { /* ... Handle error ...*/ }
  34. /* ... Send a response back to the device ... */
  35. //Parse the Response
  36. }
  37. }];
  38. [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
  39. }

iOS之内购的更多相关文章

  1. iOS 开发之内购 – AppStore

    前言本文会给大家详细介绍iOS内购,虽然之前网上也有内购的教程,但是还不够详细,我重新整理出一份教程,希望对大家有所帮助.    基于Xcode7.1.1版本,模拟器iphone6,9.1系统.    ...

  2. iOS开发之内购-AppStore

    本文会给大家详细介绍iOS内购,虽然之前网上也有内购的教程,但是还不够详细,我重新整理出一份教程,希望对大家有所帮助. 基于Xcode7.1.1版本,模拟器iphone6,9.1系统.部分地方直接摘自 ...

  3. iOS - 内购总结

        如果有人以后要在做内购这一块.希望可以好好的阅读这篇文章,虽然不是字字珠玑.但是也是本人亲人趟过了无数的坑,希望可以对大家有所帮助!  下面是在研究工程中遇到的问题(iOS 内购的流程如下 1 ...

  4. IOS内购支付server验证模式

    IOS 内购支付两种模式: 内置模式 server模式 内置模式的流程: app从app store 获取产品信息 用户选择须要购买的产品 app发送支付请求到app store app store ...

  5. IOS内购支付服务器验证模式

    IOS 内购支付两种模式: 内置模式 服务器模式 内置模式的流程: app从app store 获取产品信息 用户选择需要购买的产品 app发送支付请求到app store app store 处理支 ...

  6. Unity苹果&lpar;iOS&rpar;内购接入(Unity内置IAP)

    https://www.jianshu.com/p/4045ebf81a1c Unity苹果(iOS)内购接入(Unity内置IAP) Kakarottog                       ...

  7. iOS 内购遇到的坑

    一.内购沙盒测试账号在支付成功后,再次购买相同 ID 的物品,会提示如下内容的弹窗.您以购买过此APP内购项目,此项目将免费恢复 原因: 当使用内购购买过商品后没有把这个交易事件关,所以当我们再次去购 ...

  8. 苹果IOS内购二次验证返回state为21002的坑

    项目是三四年前的老项目,之前有IOS内购二次验证的接口,貌似很久都没用了,然而最近IOS的妹子说接口用不了,让我看看啥问题.接口流程时很简单的,就是前端IOS在购买成功之后,接收到receipt后进行 ...

  9. iOS 内购相关

    iOS 内购相关 下面总结一下过往订阅和内购的项目的代码方面的实现细节和注意事项,特别是掉单方面的处理. 后台的协议.商品ID.银行卡.内购类型.沙盒账号测试人员都由运营或者产品在苹果后台中申请处理. ...

随机推荐

  1. html5 图片上传,支持图片预览、压缩、及进度显示,兼容IE6&plus;及标准浏览器

    以前写过上传组件,见 打造 html5 文件上传组件,实现进度显示及拖拽上传,兼容IE6+及其它标准浏览器,对付一般的上传没有问题,不过如果是上传图片,且需要预览的话,就力有不逮了,趁着闲暇时间,给上 ...

  2. Android、iOS和Windows Phone中的推送技术

    推送并不是什么新技术,这种技术在互联网时代就已经很流行了.只是随着进入移动互联网时代,推送技术显得更加重要.因为在智能手机中,推送从某种程度上,可以取代使用多年的短信,而且与短信相比,还可以向用户展示 ...

  3. mysql 存储过程,以及mybatis如何调用

    说道存储过程,很多人都知道,但是真正用的人其实很少,但是在某些必要的场景,是必须使用的,虽然可以使用java代码解决,但是效率性能远不及存储过程 曾经在sqlserver 以及pgadmin上用过,m ...

  4. Intent传输包含对象的List集合

    这个其实也比较简单,我也是参考了网上的一些例子,不过我写的这个小例子亲测可用.用实现Serializable接口的方式实现. 就是说,你的list集合中的对象必须先实现Serializable接口,其 ...

  5. java类加载器学习2——自定义类加载器和父类委托机制带来的问题

    一.自定义类加载器的一般步骤 Java的类加载器自从JDK1.2开始便引入了一条机制叫做父类委托机制.一个类需要被加载的时候,JVM先会调用他的父类加载器进行加载,父类调用父类的父类,一直到*类加载 ...

  6. 将CGPoint、CGSize、CGRect等放进数组的方法

    在oc中,数组中只能存放NSObject类型的数据,所以如果将CGPoint.CGSize.CGRect这些数据存到数组中,我们需要将他们转换为对象类型才可以, 可以借助NSValue,它是用来将基本 ...

  7. 一步一步学android控件(之二十五)—— SeekBar

    SeekBar扩展自ProgressBar——在ProgressBar的基础上添加了一个用户可以拖拽的thum. SeekBar.OnSeekBarChangeListener是接收SeekBar进度 ...

  8. 尝到awk

    我前几天写的sed,这个时候继续了解它的兄弟,awk,两者都使用,一种感觉.既可以用来处理场.假设你想要做文本处理.sed删除.匹配,一些频繁更换使用,假设每一行文本,你想深入,一些每行和列处理的,例 ...

  9. Android 随着输入框控件的清除功能ClearEditText&comma;抄IOS输入框

    今天给大家带来一个非常有用的小控件ClearEditText,就是在Android系统的输入框右边增加一个小图标,点击小图标能够清除输入框里面的内容,IOS上面直接设置某个属性就能够实现这一功能.可是 ...

  10. DOM2级事件对象、添加事件、阻止默认事件、阻止冒泡事件、获取事件对象目标的兼容处理

    事件对象——兼容处理 /* * 功能: 事件对象兼容 * 参数: 表示常规浏览器的事件对象e */ function getEvent(e) { // 如果存在e存在,直接返回,否则返回window. ...