iOS应用内付费(IAP)开发步骤列表【转】

前两天和服务端同事一起,完成了应用内付费(以下简称IAP, In app purchase)的开发工作。步骤繁多,在此把开发步骤列表整理如下。因为只是步骤列表,所以并不含详细的说明教程,需要看教程的新手,可以看我附在最后的一些参考链接。

配置Developer.apple.com

登录到Developer.apple.com,然后进行以下步骤:

  1. 为应用建立建立一个不带通配符的App ID
  2. 用该App ID生成和安装相应的Provisioning Profile文件。

配置iTunes Connect

登录到iTunes Connet,然后进行以下步骤:

  1. 用该App ID创建一个新的应用。
  2. 在该应用中,创建应用内付费项目,选择付费类型,通常可选的是可重复消费(Consumable)的或是永久有效(Non-Consumable)的2种,然后设置好价格和Product ID以及购买介绍和截图即可,这里的Product ID是需要记住的,后面开发的时候需要。如下图所示: 
  3. 添加一个用于在sandbox付费的测试用户,如下图所示。注意苹果对该测试用户的密码要求 和正式账号一样,必须是至少8位,并且同时包含数字和大小写字母: 
  4. 填写相关的税务,银行,联系人信息。如下图所示: 

开发工作(ios端)

1、 在工程中引入 storekit.framework 和 #import <StoreKit/StoreKit.h> 2、 获得所有的付费Product ID列表。这个可以用常量存储在本地,也可以由自己的服务器返回。 3、 制作一个界面,展示所有的应用内付费项目。这些应用内付费项目的价格和介绍信息可以是自己的服务器返回。但如果是不带服务器的单机游戏应用或工具类应用,则可以通过向App Store查询获得。我在测试时发现,向App Store查询速度非常慢,通常需要2-3秒钟,所以不建议这么做,最好还是搞个自己的服务器吧。 4、当用户点击了一个IAP项目,我们先查询用户是否允许应用内付费,如果不允许则不用进行以下步骤了。代码如下:

1
2
3
4
5
6

1
2
3
4
5
6
if ([SKPaymentQueue canMakePayments]) {
// 执行下面提到的第5步:
[self getProductInfo];
} else {
NSLog(@"失败,用户禁止应用内付费购买.");
}

5、 我们先通过该IAP的ProductID向AppStore查询,获得SKPayment实例,然后通过SKPaymentQueue的 addPayment方法发起一个购买的操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 下面的ProductId应该是事先在itunesConnect中添加好的,已存在的付费项目。否则查询会失败。
- (void)getProductInfo {
NSSet * set = [NSSet setWithArray:@[@"ProductId"]];
SKProductsRequest * request = [[SKProductsRequest alloc] initWithProductIdentifiers:set];
request.delegate = self;
[request start];
}

// 以上查询的回调函数
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
NSArray *myProduct = response.products;
if (myProduct.count == 0) {
NSLog(@"无法获取产品信息,购买失败。");
return;
}
SKPayment * payment = [SKPayment paymentWithProduct:myProduct[0]];
[[SKPaymentQueue defaultQueue] addPayment:payment];
}

6、 在viewDidLoad方法中,将购买页面设置成购买的Observer。

1
2
3
4
5
6
7
8
9
10

1
2
3
4
5
6
7
8
9
10
- (void)viewDidLoad {
[super viewDidLoad];
// 监听购买结果
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
}

- (void)viewDidUnload {
[super viewDidUnload];
[[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
}

7、 当用户购买的操作有结果时,就会触发下面的回调函数,相应进行处理即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions {
for (SKPaymentTransaction *transaction in transactions)
{
switch (transaction.transactionState)
{
case SKPaymentTransactionStatePurchased://交易完成
NSLog(@"transactionIdentifier = %@", transaction.transactionIdentifier);
[self completeTransaction:transaction];
break;
case SKPaymentTransactionStateFailed://交易失败
[self failedTransaction:transaction];
break;
case SKPaymentTransactionStateRestored://已经购买过该商品
[self restoreTransaction:transaction];
break;
case SKPaymentTransactionStatePurchasing: //商品添加进列表
NSLog(@"商品添加进列表");
break;
default:
break;
}
}

}

- (void)completeTransaction:(SKPaymentTransaction *)transaction {
// Your application should implement these two methods.
NSString * productIdentifier = transaction.payment.productIdentifier;
NSString * receipt = [transaction.transactionReceipt base64EncodedString];
if ([productIdentifier length] > 0) {
// 向自己的服务器验证购买凭证
}

// Remove the transaction from the payment queue.
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];

}

- (void)failedTransaction:(SKPaymentTransaction *)transaction {
if(transaction.error.code != SKErrorPaymentCancelled) {
NSLog(@"购买失败");
} else {
NSLog(@"用户取消交易");
}
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}

- (void)restoreTransaction:(SKPaymentTransaction *)transaction {
// 对于已购商品,处理恢复购买的逻辑
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}

8、服务器验证凭证(Optional)。如果购买成功,我们需要将凭证发送到服务器上进行验证。考虑到网络异常情况,iOS端的发送凭证操作应该进行持久化,如果程序退出,崩溃或网络异常,可以恢复重试。

开发工作(服务端)

服务端的工作比较简单,分4步:

  1. 接收ios端发过来的购买凭证。
  2. 判断凭证是否已经存在或验证过,然后存储该凭证。
  3. 将该凭证发送到苹果的服务器验证,并将验证结果返回给客户端。
  4. 如果需要,修改用户相应的会员权限。

考虑到网络异常情况,服务器的验证应该是一个可恢复的队列,如果网络失败了,应该进行重试。 与苹果的验证接口文档在这里。简单来说就是将该购买凭证用Base64编码,然后POST给苹果的验证服务器,苹果将验证结果以JSON形式返回。 苹果AppStore线上的购买凭证验证地址是https://buy.itunes.apple.com/verifyReceipt ,测试的验证地址是:https://sandbox.itunes.apple.com/verifyReceipt 原文:http://blog.devtang.com/blog/2012/12/09/in-app-purchase-check-list/

页游那些事儿

本文是转载,作者说出了程序猿的心声啊!

页游么,无非:战争策略、模拟经营、社区养成、休闲竞技、角色扮演、社交游戏 等这几种常见类型。 游戏画面又分为: 3D、2D、2.5D、纯网页、横版。 战斗形式分为:即时、回合、战棋、文字、半即时、卡牌、自动。 游戏题材分为:音乐、武侠、科幻、魔幻、动漫、历史、射击、体育、棋牌、竞速、商业、儿童、格斗、修真、航海、玄幻、仙侠。 平台么:PC、iPhone、iPad、Android、aPad、微端(ps:WP将来也会有)。 页游测试的几个状态我们也得了解:封测、内测、公测、测试、研发中。 一般页游常用的标签有:经营、三国、隋唐、养成、塔防、海贼王、火影、足球、篮球、战机、坦克、军事、交友、Q版、西游、穿越、写实。 一般页游开发公司与运营公司的分成:1:9、2:8(居多)、3:7、4:6(极少)。。。。。再往后是个例了。 说明了一个问题,运营公司都是拿大头的,开发公司拿小的。然而现实是,很多游戏开发好了,但找不到合适的运营商。最终,这款页游就这么失败了。貌似是神仙道还是哪一款啊,本来没有找到运营商,快要破产了,但最终还是奇迹发生了,业绩是相当的那个啥的,大家都知道的,我就不说数字了。 页游么,如果抄袭的话,开发周期相对是很短的,而且相对安全性要好点,因为抄袭的对象往往是页游届最赚钱的游戏之一,抄袭了人家的赢利模式,反正不赚也不会亏多少。相反,如果自主创新研发的话,那不确定因素那就太多了,比如研发周期、技术瓶颈、资金投入、人员动荡、后期推广等系列问题都有可能导致项目的失败。一般的小公司的话,前期创业都是采用抄袭,然后赚了一点现钱,才敢去和大公司一样,自主研发。 页游圈内有个怪现象,就是同一家研发公司,有了一个成功的作品后,很难出第二款很成功的作品,即使有 也是少数。有时候,一款页游肯能是换了几批程序员的修修改改才推到玩家的窗口。 对于开发者来说,跟着小公司创业的话,机遇与风险一样的大。如果一款页游火爆了,那么你以后可以靠着这款游戏的分成、奖金快活好多年,当然前提是你的老板是很大方的。相反呢,没有成功,可能面临的是,项目奖金:无,年终奖金:无,最后呢,没办法,大家都是为了生计,程序员只能跳槽。 现状,大部分页游程序员对自己的现状都是不满意的,原因之一就是加班太多,主要是没有加班费,身为天朝的子民,你懂的。收入么,确实还可以,但其实都是加班加出来的。但话又说回来,搞页游的地方都是大城市,上海、北京、广州、深圳、杭州等。尤其前几个地方,那房价,那消费,程序员那点工资也就不算什么了。如果你的梦想是在这些地方买个房,除非你的游戏很成功很成功,并且你的老板很慷慨。不然的话,买辆车还是有可能的,这就是现实。 所以,作为程序员,赚大钱的可能性会低一点,毕竟是替别人打工么。自己创业的话,又没那么多资金、人脉关系。如果结了婚的话,那压力也就更大了,买不起房到不说。陪老婆孩子的时间都不多。 最后,说一句:程序员,你们辛苦了!(ps:也是对自己说的) 原文链接:页游那些事儿之一

AS3老鸟之路

AS3老鸟之路: 理解flash的显示列表 理解事件冒泡,理解鼠标事件等 理解flash的性能瓶颈和大多数影响性能的地方 理解帧跑道模型,知道timer和enterFrame的关联和区别 理解RSL(runtime share lib)和loader的applactiondoamin以及多模块开发/运行的优势 理解反射,类定义,库链接定义 理解常用数学公式 理解图形图像和多媒体原理,会处理图形图像 理解动画原理和帧,刷新的概念 理解小数点坐标和整数坐标点区别,flash最小坐标区间以及各种坐标变换 理解flash重绘区域和内部的运行规则 理解BitmapData,copyPixel和Blit以及Blit适用的地方,原理 理解BitmapData常用操作,如:滤镜,通道拷贝等 理解BitmapData的内存共享和释放,引用,垃圾回收(强制GC) 理解对象池 理解Socket和二进制操作 理解flash里的声音控制以及声音二进制处理/获取 理解怎么和美术配和,什么样的东西能在表现和性能之间取得平衡 理解位图和矢量图的差别以及位图缓存、 理解MousEnable和mouseChildren和常用的滤镜操作HSB等 理解AStar和路径优化 理解Avatar原理 理解地图和战斗机制和代码,会做高性能的多人同步地图 会控制操作界面 会平滑处理CPU避免峰值卡帧和优化实际运行性能,会内存换cpu,cpu换内存 理解UI制作和UI组件制作 理解如何使用flashAPI以及迅速掌握新出功能 理解网络坐标/数据同步,巡航算法 理解AIR和Flex,会使用Flex快速开发出项目中使用的工具 理解flash显示原理,脏矩形算法 理解flash常用显示对象操作 理解ObsServer设计模式和事件模型原理 理解二维矩阵和三维矩阵变换 理解手机开发和部署AIR 会使用一个3D引擎/框架 理解3D原理,理解显卡基本原理 理解MVC思想,理解23种常用设计模式 理解OOP和面向过程,结构化程序的各自优势 理解PNG8,PNG32,JPG,JPG-XR,H264,AAC,MP3,flash语音编码 理解FMS和FMS相关操作类 理解安全沙箱 深入研究各种算法,程序原理,设计方法 理解下载多线程,下载单线程,AS多线程以及Worker应用范围 理解Debug,导出工程,断点。 理解内存分析和性能分析以及优化 理解如何将一个大系统分解成多个子系统,子模块以及如何合并 会需求分析,程序逻辑分析,系统分析,项目组织 掌握敏捷开发和迭代开发,提高开发效率,适应功能需求变化 理解测试和bug处理,理解团队开发之间合作 会使用tweenMax等第三方类库,会开发类库 理解接口,继承,组合封装的作用 理解CDN和沙箱问题,常见网络知识,客户端文件部署,更新操作版本控制 SVN版本控制 理解领域知识,理解游戏 理解SWC的作用(导出代码,UI界面,资源等,以及配合RSL) 会使用自动构建界面/UI组件技术 理解炼金术,会从C/C++传统游戏开发中学习经验技巧甚至代码 会掌控一个项目以及解决项目中出现的任何(注意这个词)技术问题 会开发一个项目专用框架,会封装项目底层 会制作一个游戏2D/3D引擎 掌握独立学习钻研的方法。 做事情(写程序)要有效率,并且稳定。 相信自己可以比别人做得更好并努力去做。 保持快乐心态并成为多面手,提高综合素质和能力(不仅限于编程)

判断线段和矩形是否相交

阅读本文,你可以了解 AS3中判断线段和矩形是否相交的一种方法。 实例如下: RectLine

  源码如下:(判断方法来自网上,原方法是java的) [codesyntax lang=”php”]

package
{
import flash.display.Sprite;
import flash.events.MouseEvent;
import flash.text.TextField;

[SWF(width=375,height=300,backgroundColor=”0xeeeeee”)]
public class RectLine extends Sprite
{
private var txt:TextField;
public function RectLine()
{

this.txt = new TextField();
addChild(txt);

stage.addEventListener(MouseEvent.CLICK,ckHandler);
ckHandler(null);
}

private function ckHandler(e:MouseEvent):void
{
graphics.clear();
graphics.beginFill(0x00ffff);
graphics.drawRect(100,100,200,50);
graphics.endFill();

var a:int = Math.random()*375;
var b:int = Math.random()*300;
var c:int = Math.random()*375;
var d:int = Math.random()*300;

graphics.lineStyle(2,0xff0000);
graphics.moveTo(a,b);
graphics.lineTo(c,d);
trace(isLineIntersectRectangle(a,b,c,d,100,100,300,150));
var isF:Boolean = isLineIntersectRectangle(a,b,c,d,100,100,300,150);
txt.text = “是否相交:”+isF;
}

/**

判断线段是否在矩形内


* 先看线段所在直线是否与矩形相交,
* 如果不相交则返回false,
* 如果相交,
* 则看线段的两个点是否在矩形的同一边(即两点的x(y)坐标都比矩形的小x(y)坐标小,或者大),
* 若在同一边则返回false,
* 否则就是相交的情况。
* @param linePointX1 线段起始点x坐标
* @param linePointY1 线段起始点y坐标
* @param linePointX2 线段结束点x坐标
* @param linePointY2 线段结束点y坐标
* @param rectangleLeftTopX 矩形左上点x坐标
* @param rectangleLeftTopY 矩形左上点y坐标
* @param rectangleRightBottomX 矩形右下点x坐标
* @param rectangleRightBottomY 矩形右下点y坐标
* @return 是否相交
*/
private function isLineIntersectRectangle(linePointX1:Number,
linePointY1:Number,
linePointX2:Number,
linePointY2:Number,
rectangleLeftTopX:Number,
rectangleLeftTopY:Number,
rectangleRightBottomX:Number,
rectangleRightBottomY:Number):Boolean
{
var lineHeight:Number = linePointY1 - linePointY2;
var lineWidth:Number = linePointX2 - linePointX1; // 计算叉乘
var c:Number = linePointX1 * linePointY2 - linePointX2 * linePointY1;
if ((lineHeight * rectangleLeftTopX + lineWidth * rectangleLeftTopY + c >= 0 && lineHeight * rectangleRightBottomX + lineWidth * rectangleRightBottomY + c <= 0)
(lineHeight * rectangleLeftTopX + lineWidth * rectangleLeftTopY + c <= 0 && lineHeight * rectangleRightBottomX + lineWidth * rectangleRightBottomY + c >= 0)
(lineHeight * rectangleLeftTopX + lineWidth * rectangleRightBottomY + c >= 0 && lineHeight * rectangleRightBottomX + lineWidth * rectangleLeftTopY + c <= 0)
(lineHeight * rectangleLeftTopX + lineWidth * rectangleRightBottomY + c <= 0 && lineHeight * rectangleRightBottomX + lineWidth * rectangleLeftTopY + c >= 0))
{

if (rectangleLeftTopX > rectangleRightBottomX) {
var temp:Number = rectangleLeftTopX;
rectangleLeftTopX = rectangleRightBottomX;
rectangleRightBottomX = temp;
}
if (rectangleLeftTopY < rectangleRightBottomY) {
var temp1:Number = rectangleLeftTopY;
rectangleLeftTopY = rectangleRightBottomY;
rectangleRightBottomY = temp1; }
if ((linePointX1 < rectangleLeftTopX && linePointX2 < rectangleLeftTopX)
(linePointX1 > rectangleRightBottomX && linePointX2 > rectangleRightBottomX)
(linePointY1 > rectangleLeftTopY && linePointY2 > rectangleLeftTopY)
(linePointY1 < rectangleRightBottomY && linePointY2 < rectangleRightBottomY)) {
return false;
} else {
return true;
}
} else {
return false;
}
}
}
}

[/codesyntax] 延伸思考:1.旋转后的矩形 是否还能用这个方法进行判断? 2.直线与其他形状(如椭圆)如何判断相交? 延伸阅读:公式汇总 http://blog.sqstudio.com/formula

Starling中文站翻译第三季

目前Starling中文站翻译第三季火热进行中,有能力有时间的你,不妨也去为社区做点贡献。 作者一直对英语有阴影,上学时英语好烂,无奈搞了IT这一行,英语也要过关,国内和国外的技术还是有很大差距。多年来一直坚持着记单词,至少先能看懂文档再说,有道单词本记得满满的,^_^! 如今看到 Starling中文站在认领翻译,斗胆也去认领了几篇,好在翻译后也会有管理员审核修改,不至于太误导别人。接了翻译后,这几天也是各种有道,谷歌,偶尔问下别人,总算把自己认领的几篇翻译完了。提交了以后,管理员一看,还有几篇没人认领,故而又翻译了一篇,也越来越顺手了,嘿嘿。颇感欣慰! 以下是作者认领的几篇,如果你发现有错误,可以直接联系我,以进行修改。 1、List of Feathers Features 2、PickerList 3、ProgressBar 4、FlashDevelop Starling中文站后面还会引入其他的相关翻译,有志之士,积极参与。 人人为我,我为人人! starling翻译