[iOS]应用内支付(内购)的个人开发过程及坑!

本文基于XcodeVersion 7.3 (7D175)版本,手机是iPhone 6,9.3系统。

一. 创建测试App

首先你需要登录 App的ItunesConnection,你会看到如下界面
这里写图片描述

简单的介绍一下这几个选项
1.我的App主要用于管理自己的App应用,例如编辑资料,上架,下架等。
2.销售和趋势主要是来查看App在各个平台的下载量,收入等方面数据,里面有曲线图等图文结合的方式给我们参考。
3.付款和财务报告显示的是你的收入以及付款等相关信息。
4.iAd主要是跟广告有关,开发者可以登录到Workbench,通过iAd对应用的广告进行控制。
5.用户和职能用于生成相应账号,例如苹果沙河测试账号。
6.协议,税务和银行业务则是你银行相关账户的信息设置。
在这里我们选择第一个选项,我的App, 然后点击左上角的加号,新建一个用来测试用的App。
这里写图片描述

点新建 App,会出现新建窗口;
这里写图片描述

在这里有几个需要填写的地方,名称自己取,平台IOS,语言选择了简体中文,套装ID也就是你的Bundle Identifier,需要你在Certificates页面 申请BundleID,SKU可以理解为用户看一看到的唯一标示,会体现在你的app的App Store的链接中。

二.添加内购

App创建好之后,我们打开创建的App,在左上角选择功能,会看到左侧的App 内购买项目。我们点击右下角的加号,为App添加内购项目。
这里写图片描述

之后我们会看到类型的选项,如下图

这里写图片描述

官方的注释写的很清楚了,只在这里简单的说下前两种:
- 消耗型项目 就像你玩游戏需要买金币,买钻石等,只要花钱就可以无限次的购买
- 非消耗型项目 就像你在App Store购买App,买了一次之后就不用再买第二次,你拥有永久使用权。
在我们的app中,是充值会员,所以选择的是第一种,可以无限次购买。

这里写图片描述

这里有几个选项,需要填写商品名称,产品ID以及价格等级,简单说明一下
1. 商品名称根据你的消费道具的实际意义来说明,比如“100颗宝石”,“100金币”等。
2. 产品ID是比较重要的,由项目自定义,只要唯一即可,因为测试,我在这里随便填写的123,在实际应用中,一定要认真填写。
3. 价格等级的话“查看价格表”中有对应的说明,可以对照着表中每个国家的货币价格与等级来选择
接下来是语言选择,和上传快照如下图

这里写图片描述

点击添加语言,填写名称和描述,这里我们依然选择简体中文,如下

这里写图片描述

审核备注,根据实际情况填写,可以不填。而下面的屏幕快照,则是商品图片,以像素为单位,最低尺寸为321,390,尺寸需求如下图,上传即可。

这里写图片描述

到这里为止, 我们的内购项目则添加完成。接下来则是测试阶段了。

三.申请沙盒测试账号(用来测试购买项目)

这个账号,是利用苹果的沙盒测试环境来模拟AppStore的购买流程,你肯定不会想要用真实RMB去购买测试吧?
首先我们回到iTunes Connect中,在这里我们选择用户和职能。

这里写图片描述

然后在上面的第三个选项沙箱技术测试员中点击加号,添加测试员。

这里写图片描述

在信息填写页面只简单说两句。
所有信息都可以随意填写,不用管是否真实。
App Store地区选择,一定要选对,它对应的是你创建的App的地区, 你App是中国的话, 在这里我们依然选择中国。
此账号只能用来测试,不要在正式的appstore上使用
填写完毕,点击保存后,我们则生成一个测试账号,当然这个账号是可以随时删除和添加的。

这里写图片描述

之后终于到了写代码的时候了,点开你的Xcode创建你的项目!
大部分代码都可以在.m文件中实现。

#import "ViewController.h"
#import <StoreKit/StoreKit.h>
#import "SVProgressHUD.h" @interface ViewController ()<SKPaymentTransactionObserver,SKProductsRequestDelegate> @property (nonatomic,copy) NSString *currentProId; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom]; button.frame = CGRectMake(100, 100, 100, 100); button.backgroundColor = [UIColor greenColor]; [button setTitle:@"6元" forState:UIControlStateNormal]; [button addTarget:self action:@selector(btnClick:) forControlEvents:UIControlEventTouchDown]; [self.view addSubview:button]; } - (void)btnClick:(UIButton *)button { [[SKPaymentQueue defaultQueue] addTransactionObserver:self]; _currentProId = @"123"; if([SKPaymentQueue canMakePayments]){ [self requestProductData:product]; }else{ NSLog(@"不允许程序内付费"); } } //去苹果服务器请求商品 - (void)requestProductData:(NSString *)type{ NSLog(@"-------------请求对应的产品信息----------------"); [SVProgressHUD showWithStatus:nil maskType:SVProgressHUDMaskTypeBlack]; NSArray *product = [[NSArray alloc] initWithObjects:type,nil]; NSSet *nsset = [NSSet setWithArray:product]; SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:nsset]; request.delegate = self; [request start]; } //收到产品返回信息 - (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{ NSLog(@"--------------收到产品反馈消息---------------------"); NSArray *product = response.products; if([product count] == 0){ [SVProgressHUD dismiss]; 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:_currentProId]){ p = pro; } } SKPayment *payment = [SKPayment paymentWithProduct:p]; NSLog(@"发送购买请求"); [[SKPaymentQueue defaultQueue] addPayment:payment]; } //请求失败 - (void)request:(SKRequest *)request didFailWithError:(NSError *)error{ [SVProgressHUD showErrorWithStatus:@"支付失败"]; NSLog(@"------------------错误-----------------:%@", error); } - (void)requestDidFinish:(SKRequest *)request{ [SVProgressHUD dismiss]; NSLog(@"------------反馈信息结束-----------------"); } //沙盒测试环境验证 #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:@"123"]) { int purchasedCount=[defaults integerForKey:productIdentifier];//已购买数量 [[NSUserDefaults standardUserDefaults] setInteger:(purchasedCount+1) forKey:productIdentifier]; }else{ [defaults setBool:YES forKey:productIdentifier]; } //在此处对购买记录进行存储,可以存储到开发商的服务器端 }else{ NSLog(@"购买失败,未通过验证!"); } } //监听购买结果 - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transaction{ for(SKPaymentTransaction *tran in transaction){ switch (tran.transactionState) { case SKPaymentTransactionStatePurchased:{ NSLog(@"交易完成"); // 发送到苹果服务器验证凭证 [self verifyPurchaseWithPaymentTransaction]; [[SKPaymentQueue defaultQueue] finishTransaction:tran]; } break; case SKPaymentTransactionStatePurchasing: NSLog(@"商品添加进列表"); break; case SKPaymentTransactionStateRestored:{ NSLog(@"已经购买过商品"); [[SKPaymentQueue defaultQueue] finishTransaction:tran]; } break; case SKPaymentTransactionStateFailed:{ NSLog(@"交易失败"); [[SKPaymentQueue defaultQueue] finishTransaction:tran]; [SVProgressHUD showErrorWithStatus:@"购买失败"]; } break; default: break; } } } //交易结束 - (void)completeTransaction:(SKPaymentTransaction *)transaction{ NSLog(@"交易结束"); [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; } - (void)dealloc{ [[SKPaymentQueue defaultQueue] removeTransactionObserver:self]; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. } @end

在这里需要注意几点,
1. 代码中的_currentProId所填写的是你的购买项目的的ID,这个和第二步创建的内购的productID要一致;本例中是 123。
2. 在监听购买结果后,一定要调用[[SKPaymentQueue defaultQueue] finishTransaction:tran];来允许你从支付队列中移除交易。
3. 沙盒环境测试appStore内购流程的时候,请使用没越狱的设备。
4. 请务必使用真机来测试,一切以真机为准。
5. 项目的Bundle identifier需要与您申请AppID时填写的bundleID一致,不然会无法请求到商品信息。
6. 真机测试的时候,一定要退出原来的账号,才能用沙盒测试账号
7. 二次验证,请注意区分宏, 测试用沙盒验证,App Store审核的时候也使用的是沙盒购买,所以验证购买凭证的时候需要判断返回Status Code决定是否去沙盒进行二次验证,为了线上用户的使用,验证的顺序肯定是先验证正式环境,此时若返回值为21007,就需要去沙盒二次验证,因为此购 买的是在沙盒进行的。

附:苹果支付错误目录

Status CodeDescription
21000The App Store could not read the JSON object you provided.
21002The data in the receipt-data property was malformed or missing.
21003The receipt could not be authenticated.
21004The shared secret you provided does not match the shared secret on file for your account.Only returned for iOS 6 style transaction receipts for auto-renewable subscriptions.
21005The receipt server is not currently available.
21006This receipt is valid but the subscription has expired. When this status code is returned to your server, the receipt data is also decoded and returned as part of the response.Only returned for iOS 6 style transaction receipts for auto-renewable subscriptions.
21007This receipt is from the test environment, but it was sent to the production environment for verification. Send it to the test environment instead.
21008This receipt is from the production environment, but it was sent to the test environment for verification. Send it to the production environment instead.

摘自:http://blog.csdn.net/darling_shadow/article/details/51538267

参考:
1. iOS开发之内购-AppStore;
2. 苹果官方文档

转载于:https://www.cnblogs.com/MyBlogZH/p/5563468.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/293969.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Android之Intent 序列化反序列化

我们做截屏功能的时候&#xff0c;因为有2个进程&#xff0c;本来是把intent和MediaProjection放到Application里面&#xff0c;但是由于跨进程了&#xff0c;所以数据拿不到&#xff0c;就采用了Parcel 序列化出错,未找到出错的原因,找其它的解决方法: 查看Intent 的源代码, 发…

农商银行招聘计算机人员考什么,农商银行招聘考试题都考什么?

整理了农商农商一、行政职业能力测试类农商银行行测考试题型主要以选择题形式出现。主要包括言语理解、数量关系、判断推理、资料分析、常识五大部分。二、英语类农商银行考试英语部分&#xff1a;一般银行英语考试内容包括英语词汇与语法、英汉互译、改错、完型填空和阅读理解…

一步步学习微软InfoPath2010和SP2010--第八章节--使用InfoPath表单Web部件

本章中&#xff0c;你将学习到&#xff1a; 1. 配置Web部件设置 2. 创建Web部件连接 3. 创建表单参数 4. 使用其他浏览器表单参数 你可以使用InfoPath表单Web部件&#xff08;Microsoft SharePoint2010新引入的&#xff09;在SharePoint企业版或Microsoft…

讲一讲应用服务的新鲜事儿

微软中国MSDN 点击上方蓝字关注我们为了新功能的发布&#xff0c;以及 Linux 和 Windows 的改进&#xff0c;Azure App Service 团队付出了非常多的努力。很开心的是&#xff0c;我们看到了 Windows Containers 的正式版本&#xff0c;并可应用于 App Service 环境 v3 上。此外…

运维自动化之基于python语言的文字界面的运维管理软件

之前开发了phpmysqlshell运维监控系统&#xff0c;监控起来很方便&#xff0c;但在运维管理方便还是不能实现&#xff0c;所以最近打算使用python语言编写一套的运维管理系统&#xff0c;可以使用单台或多台机器同时管理与部署等功能&#xff0c;实现类似func、triaquae等管理软…

Android之4.0新特性

Android 4.0 平台 API等级:14 Android 4.0 是一次重要的平台发布版,为用户和应用程序开发者增加了大量的新特性。在下面我们将讨论的所有新特性和API中,因为它将 Android 3.x 版本中广泛使用的API和全息图像主题带给了小屏幕设备,因此我们说 Android 4.0 是一次重要的平…

iOS中的动画

2019独角兽企业重金招聘Python工程师标准>>> iOS中的动画 Core Animation Core Animation是一组非常强大的动画处理API,使用它能做出非常绚丽的动画效果,而且往往是事半功倍,使用它需要添加QuartzCore .framework和引入对应的框架<QuartzCore/QuartzCore.h>…

Debian7 apt源设置

刚装完系统时是没有 apt-spy 的&#xff0c;这时候我们可以暂时先找个可用的源代替&#xff0c;如&#xff08;写在 /etc/apt/sources.list 中&#xff09;&#xff1a; deb http://http.us.debian.org/debian/ stable main 执行以下命令更新软件包列表&#xff1a; apt-get up…

Android之6.0上的重要变化(一)

伴随着众多新特性和新功能,Android6.0(API level 23)在系统和API上都有着诸多的改变。本文着重介绍几个关键变化,以帮助你理解这些改变对你的APP产生的影响。 一、运行时权限检查(Runtime Permisssions) 此次发布引入了一个新的权限管理模型,使得用户能够在运行时控…

全世界70亿人同时起跳,地球会天崩地裂?答案可能让你难以置信

全世界只有3.14 % 的人关注了爆炸吧知识地球表示微微一笑不得不说&#xff0c;模友们的脑袋里总是充满了奇思妙想&#xff0c;比如超模君最近就收到了这么个问题&#xff1a;首先&#xff0c;这问题真就属于一看很有趣&#xff0c;细想全是BUG的那种。且不说怎么让70亿人同时聚…

计算机应用基础0006 18春在线作业1,《计算机应用基础0006》16春在线作业2

《计算机应用基础0006》16春在线作业2一、单选题(共 50 道试题&#xff0c;共 100 分。)1. 在Excel 2010的主界面中&#xff0c;不包含的选项卡是__B__&#xff1a; A. 公式B. 函数C. 插入D. 开始2. 以下属于无线接入互联网的是__D__&#xff1a; A. GPRS/CDMA B. WLAN C. 3G D…

2021.NET Conf China上的GraphQL

今天给大家分享.NET中的GraphQL&#xff0c;半个小时&#xff0c;没有把所有的代码敲出来&#xff0c;高估了自己的写码速度&#xff0c;所以通过这个文章分享出来。1、核心PPT2、主要代码using Conf2021GraphQL; using Conf2021GraphQL.Models; using Microsoft.EntityFramewo…

MyISAM和InnoDB的区别

mysql的存储引擎包括&#xff1a;MyISAM、InnoDB、BDB、MEMORY、MERGE、EXAMPLE、NDBCluster、ARCHIVE、CSV、BLACKHOLE、FEDERATED等&#xff0c;其中InnoDB和BDB提供事务安全表&#xff0c;其他存储引擎都是非事务安全表。 最常使用的2种存储引擎:  1.Myisam是Mysql的默认存…

JavaScript中“javascript:void(0) ”是什么意思

来源&#xff1a; <a href"javascript:test();void(0);">here</a> 此处&#xff1a;Javascript中void是一个操作符&#xff0c;该操作符指定要计算一个表达式但是不返回值。 void 操作符用法格式如下&#xff1a;1. javascript:void (expression)2. java…

Android之6.0上的重要变化(二)

十、Android KeyStore变化 此版本上Android Keystore provider不再支持DSA,仍旧支持ECDSA。 锁屏密码在(如用户或设备管理器)禁用或重置的情况下,不需要加密部分将不再被删除,而加密部分则会被删除。 十一、Wi-Fi和网络变化(Wi-Fi and Networking Changes) 此版本…

Oracle常用语句

一、导出有条件数据: exp zh/mmsignon tables(tablename1) filed:\aa.dmp query\"where IDC8ECD78D123C42AEA64910C30325CEA7\" 二、修改字符集&#xff1a; a.查看字符集 select * from v$nls_parameters b.客户端:cmd下运行set NLS_LANGSIMPLIFIED CHINESE_CHINA.Z…

全国二级计算机理论知识,2021年度全国计算机等级考试二级MSOffice常考知识点基础知识部分.doc...

计算机发展、类型及其应用领域。计算机(computer)是一种能自动、高速进行大量算术运算和逻辑运算电子设备。 其特点为&#xff1a;速度快、精度高、存储容量大、通用性强、具备逻辑判断和自动控制能力。第一台计算机&#xff1a;ENIAC&#xff0c;美国&#xff0c;1946年 宾夕法…

Rust 社区求变,PHP 大旗不倒?

文 | 一君出品 | OSC开源社区&#xff08;ID&#xff1a;oschina2013&#xff09;上月底发生了两件开源语言相关的大事。一是 JetBrains 牵头成立 PHP 基金会&#xff0c;希望能确保语言的长寿和繁荣。二是 Rust 审核团队集体辞职&#xff0c;理由是 Rust 核心团队造成了“寡头…