Unity游戏iOS AppStore 内付费接入(In app purchase)
前阵子忙着赶项目没什么时间做总结,前两个星期iOS审核通过,项目也顺利在北美上线了。刚好公司组织旅游回来,抽空总结写Unity iOS 内付费的接入。
1、前置条件:
(1) 苹果开发者账号(99美刀一年,没有的话可以在某宝上买个p12正式,自己搞搞开发还是比较合算);
(2) 创建好应用的bundle ID 及相关开发证书和描述文件;
(3) Itunes Connect 创建对应的App及设置好内购的商品。
2、内购商品创建
因为最近苹果开发者中心又各种改革,创建流程和在网上很多教程都不一样,防止接入者采坑,这里简单的描述下:
(1)创建App:打开itunes connect -> My App -> + (左上角的加号) ,然后填写相关信息,比如bundleID(套装 ID),版面号等,SKU码随便填就好了,点击创建完成
(2)点击你刚创建好的App,选择 app 内购买项目(In app purchase),事先要设置好收款的信用卡账户,否则系统会提示你去设置。进入App 内购设置项后,点击Create New,选择Consumable (消耗型项目),填写相关信息就好了,ProductID(产品ID)必须唯一,最后添加语言和截屏就完成了,下面我们进入编程阶段。
3、Unity与Object-C交互
(1) Unity和Object-C的交互:Unity官方提供一种交互方式,采用C++ Object-C混编的模式,Unity通过C++ 调用Object-C代码,从而实现Unity与Object-C的交互;
(2) 创建交互的.h 和.mm文件,可能有些人不知怎么在Xcode中创建.mm文件,最简单的方法就是创建.m文件后重命名为.mm文件;
(3) 为了简化Unity和Object-C的交互,我们使用单例的方式实现,最后我们仅需把AppStorePayForUnity.h 和AppStorePayForUnity.mm两个文件放在Unity的Plugins/iOS目录下即可,Unity导出工程后会添加这两个文件。
4、核心实现
(1) 单例创建::在工程中引入storekit.framework 和 #import <StoreKit/StoreKit.h>,并添加SKProductsRequestDelegate 和SKPaymentTransactionObserver两个委托,用于监听购买的回调,具体代码如下:
1 // AppStorePayForUnity.h 2 #ifndef _AppStorePayForUnity_h 3 #define _AppStorePayForUnity_h 4 5 #import <StoreKit/StoreKit.h> 6 7 @interface AppStorePayForUnity : NSObject <SKProductsRequestDelegate, SKPaymentTransactionObserver> 8 { 9 } 10 11 @property (nonatomic, retain) NSString *mCallBackObjectName; 12 @property (nonatomic, retain) NSString *mServerId; 13 @property (nonatomic, retain) NSString *mOrderId; 14 @property (nonatomic, retain) NSString *mExInfo; 15 16 + (AppStorePayForUnity*) instance;
1 // AppStorePayForUnity.h 2 #import <Foundation/Foundation.h> 3 #import "AppStorePayForUnity.h" 4 5 #ifndef __APPSTORE_IN_UNITY__ 6 #define __APPSTORE_IN_UNITY__ 7 #endif 8 9 #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000 10 #define IOS7_SDK_AVAILABLE 1 11 #endif 12 13 #if defined(__cplusplus) 14 extern "C" {15 #endif 16 extern void UnitySendMessage(const char* obj, const char* method, const char* msg); 17 extern NSString* AppStoreCreateNSString (const char* string); 18 #if defined(__cplusplus) 19 } 20 #endif 21 22 static AppStorePayForUnity* _instance = nil; 23 24 @implementation AppStorePayForUnity 25 26 @synthesize mCallBackObjectName; 27 @synthesize mServerId; 28 @synthesize mOrderId; 29 @synthesize mExInfo; 30 31 //使用同步创建 保证多线程下也只有一个实例 32 + (AppStorePayForUnity *)instance 33 {34 @synchronized(self) 35 {36 if (_instance == nil) 37 {38 _instance = [[AppStorePayForUnity alloc] init]; 39 } 40 } 41 return _instance; 42 } 43 44 - (id)init 45 {46 self = [super init]; 47 48 if (self) 49 {50 // 监听购买结果 51 [[SKPaymentQueue defaultQueue] addTransactionObserver:self]; 52 } 53 return self; 54 } 55 56 -(void) dealloc 57 {58 [[SKPaymentQueue defaultQueue] removeTransactionObserver:self]; 59 self.mCallBackObjectName = nil; 60 [super dealloc]; 61 }
(2)Unity必须事先初始化,设置好回调的GameObject对象,用于传递支付信息到Unity游戏中。
1 //初始化 设置回调的对象 2 - (void)initAppStorePay:(NSString*)callBackName 3 { 4 self.mCallBackObjectName = callBackName; 5 }
(3)当用户点击了一个IAP项目,我们先查询用户是否允许应用内付费,如果不允许则不用进行以下步骤了。代码如下:
1 //是否有购买权限 2 - (BOOL)canMakePay 3 { 4 return [SKPaymentQueue canMakePayments]; 5 }
(4) 我们先通过该IAP的ProductID向AppStore查询,获得SKPayment实例,然后通过SKPaymentQueue的 addPayment方法发起一个购买的操作
1 // 开始购买商品 2 - (void)startBuyProduct:(NSString *)serverId orderId:(NSString *)orderId exInfo:(NSString *)exInfo productId:(NSString*)productId 3 { 4 if (self.canMakePay) 5 { 6 NSLog(@"-------------- 开始购买 --------------"); 7 self.mServerId = serverId; 8 self.mOrderId = orderId; 9 self.mExInfo = exInfo; 10 [self getProductInfoById:productId]; 11 } 12 else 13 {14 NSLog(@"------------ App不用允许内购 --------------"); 15 } 16 } 17 18 // 下面的ProductId应该是事先在itunesConnect中添加好的,已存在的付费项目。否则查询会失败。 19 -(void)getProductInfoById:(NSString*)productID 20 {21 NSLog(@"----getProductInfoById---------id: %@", productID); 22 NSArray *product = nil; 23 product = [[NSArray alloc] initWithObjects:productID, nil]; 24 NSSet *set = [NSSet setWithArray:product]; 25 SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:set]; 26 //设置并启动监听 27 request.delegate = self; 28 [request start]; 29 //[product rele]; 30 } 31 32 33 // 以上查询的回调函数 34 - (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response 35 {36 NSLog(@"收到商品反馈"); 37 NSArray *myProduct = response.products; 38 if (myProduct.count == 0) 39 {40 NSLog(@"无法获取产品信息,购买失败。"); 41 return; 42 } 43 44 // test 45 for(SKProduct *temp in myProduct) 46 {47 NSLog(@"ProductInfo"); 48 NSLog(@"SKProduct 描述信息%@", [temp description]); 49 NSLog(@"Product id: %@ 价格%@", temp.productIdentifier, temp.price); 50 } 51 52 NSLog(@"发送购买请求"); 53 SKPayment * payment = [SKPayment paymentWithProduct:myProduct[0]]; 54 [[SKPaymentQueue defaultQueue] addPayment:payment]; 55 NSLog(@"-------------------%@", payment); 56 }
(5) 购买结果监听:当苹果服务器返回购买结果时回自动调用paymentQueue方法:
1 - (void) paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions 2 { 3 NSLog(@"------- payment Queue -----"); 4 for (SKPaymentTransaction *transaction in transactions) 5 { 6 switch (transaction.transactionState) 7 { 8 case SKPaymentTransactionStatePurchased://交易完成 9 NSLog(@"transactionIdentifier = %@", transaction.transactionIdentifier); 10 [self completeTransaction:transaction]; 11 break; 12 case SKPaymentTransactionStateFailed://交易失败 13 [self failedTransaction:transaction]; 14 break; 15 case SKPaymentTransactionStateRestored://已经购买过该商品 16 [self restoreTransaction:transaction]; 17 break; 18 case SKPaymentTransactionStatePurchasing://商品添加进列表 19 NSLog(@"商品添加进列表"); 20 break; 21 default: 22 break; 23 } 24 } 25 } 26 27 // 支付成功 28 - (void) completeTransaction:(SKPaymentTransaction*)transaction 29 {30 NSLog(@"--------------completeTransaction--------------"); 31 // Remove the transaction from the payment queue. 32 [[SKPaymentQueue defaultQueue] finishTransaction: transaction]; 33 34 // Your application should implement these two methods. 35 NSString * productIdentifier = transaction.payment.productIdentifier; 36 37 if([productIdentifier length] > 0) 38 {39 NSLog(@"productIdentifier : %@", productIdentifier); 40 } 41 42 // 向自己的服务器发送购买凭证 43 [self checkReceiptToServer:transaction]; 44 #ifdef __APPSTORE_IN_UNITY__ 45 // 通知 unity 购买成功 46 UnitySendMessage(self.mCallBackObjectName.UTF8String, 47 "DebugUnityMessage", "BuySuccess"); 48 UnitySendMessage(self.mCallBackObjectName.UTF8String, 49 "BuyProductSuccess", productIdentifier.UTF8String); 50 #endif 51 } 52 53 // 支付失败 54 - (void) failedTransaction:(SKPaymentTransaction*)transaction 55 {56 if(transaction.error.code != SKErrorPaymentCancelled) 57 {58 NSLog(@"购买失败"); 59 #ifdef __APPSTORE_IN_UNITY__ 60 UnitySendMessage(self.mCallBackObjectName.UTF8String, 61 "DebugUnityMessage", "购买失败"); 62 #endif 63 } 64 else 65 {66 NSLog(@"用户取消交易"); 67 #ifdef __APPSTORE_IN_UNITY__ 68 UnitySendMessage(self.mCallBackObjectName.UTF8String, 69 "DebugUnityMessage", "用户取消交易"); 70 #endif 71 } 72 [[SKPaymentQueue defaultQueue] finishTransaction: transaction]; 73 #ifdef __APPSTORE_IN_UNITY__ 74 UnitySendMessage(self.mCallBackObjectName.UTF8String, 75 "BuyProudctFailed", "购买失败"); 76 #endif 77 } 78 79 // 对于已购商品,处理恢复购买的逻辑 80 - (void) restoreTransaction:(SKPaymentTransaction*)transaction 81 {82 [[SKPaymentQueue defaultQueue] finishTransaction: transaction]; 83 #ifdef __APPSTORE_IN_UNITY__ 84 UnitySendMessage(self.mCallBackObjectName.UTF8String, 85 "DebugUnityMessage", "恢复已购商品"); 86 #endif 87 } 88 89 - (void) checkReceiptToServer:(SKPaymentTransaction*)transaction 90 {91 NSString *transactionId = transaction.transactionIdentifier; 92 NSLog(@"-----transactionId--------- %@", transactionId); 93 94 NSData *receipt = nil; 95 NSString *strVersion = nil; 96 97 #if IOS7_SDK_AVAILABLE 98 NSLog(@"---------------------SDK 7.1 ---------------"); 99 NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL]; 100 receipt = [NSData dataWithContentsOfURL:receiptURL]; 101 strVersion = @"iOS7"; 102 #else 103 NSLog(@"---------------------SDK 6.1 ---------------"); 104 receipt = transaction.transactionReceipt; 105 strVersion = @"iOS6"; 106 #endif 107 108 109 if(!receipt) 110 {111 //no local receipt -- handle the error 112 113 } 114 115 //NSLog(@"receipt data is :%@",receipt); 116 NSError *error; 117 //这个是发给appstore服务端的 118 NSDictionary *requestContents = @{119 @"receipt-data": [receipt base64EncodedStringWithOptions:0], 120 }; 121 122 NSData *requestData = [NSJSONSerialization dataWithJSONObject:requestContents options:0 error:&error]; 123 124 //TODO:服务端验证 125 //……. 126 }
5、至此iOS内付费接入已经完成了,附上测试截图一张,详细代码见网盘:http://pan.baidu.com/s/1o60snFk
参考链接:
1、http://blog.devtang.com/blog/2012/12/09/in-app-purchase-check-list/
2、http://game.dapps.net/gamedev/in-app-purchase/3080.html
3、http://www.himigame.com/iphone-cocos2d/550.html
4、http://www.xuanyusong.com/archives/521
Unity游戏iOS AppStore 内付费接入(In app purchase)相关推荐
- iOS应用内购买(In App Purchase)总结
先附上几篇文章: 1.In App Purchases: A Full Walkthrough 这篇文章里说的都很详尽了,代码什么的基本可以照搬. 2.Store Kit Guide(In App P ...
- iOS应用内付费(IAP)开发步骤列表
iOS应用内付费(IAP)开发步骤列表 前两天和服务端同事一起,完成了应用内付费(以下简称IAP, In app purchase)的开发工作.步骤繁多,在此把开发步骤列表整理如下.因为只是步骤列表, ...
- iOS应用内付费详解
Himi 原创, 欢迎转载,转载请在明显处注明! 谢谢. 原文地址:http://blog.csdn.net/xiaominghimi/article/details/6937097 //--201 ...
- 如何使得客户端和服务器端完美配合做IOS应用内付费
配置Developer.apple.com 登录到Developer.apple.com,然后进行以下步骤: 为应用建立建立一个不带通配符的App ID 用该App ID生成和安装相应的Provisi ...
- IOS 应用内打开三方地图app直接导航(高德坐标)
疯狂试探 - (BOOL)canOpenURL:(NSURL *)url NS_AVAILABLE_IOS(3_0); 常用地图应用的url Scheme: //百度地图 baidumap //高德地 ...
- iOS应用内置付费详尽攻略
我最近正在制作的一个程序里面,我就决定先把程序免费(其中只包含一个故事),然后把更多的故事放在in-app purchase里面.在这篇教程里面,你将会学到如何使用程序内置付费来解琐本地程序里面的内容 ...
- ios应用内支付过程(使用苹果原生支付方式
因为ios政策问题,如果开发者需要在ios客户端中加入购买(虚拟货币)项目,需要使用ios应用内付费IAP这种方式,这也是很多苹果APP不和安卓共通的原因.因为苹果需要抽取%30的利益,详细原因就不赘 ...
- 虚拟内购服务器,苹果APP内购客户付款成功,没收到相应虚拟产品的解决办法
一.引导用户走申请苹果的退款 1.告知用户新版本可以使用支付宝.微信支付,更划算 2.苹果可申请90天以内的退款,一般情况申请后48小时内就有反馈. 二.用户坚持补偿虚拟产品 1.如果是今天的交易,让 ...
- Cocos2d-x使用iOS游戏内付费IAP(C++篇)
source file url: http://www.tairan.com/archives/5515 Cocos2d-x使用iOS游戏内付费IAP(C++篇) 前期准备 设备与账号 在开始编码之前 ...
- 如何将Unity游戏构建到iOS设备
介绍 本文章将构建一个示例Unity项目到iOS设备进行测试( 非常详细,简直细到了每个操作,每个情况),原文出自Unity官网教程 Building your Unity game to an iO ...
最新文章
- linux下压缩并分割稀疏文件
- server 2008访问Server 2003数据慢
- SVN的使用(服务端与客户端)
- Dell在CRM的创新及该模式的推广
- UVa712 S-Trees满二叉树
- docker 相关操作
- SAP License:CO07利润中心必输
- Clustered Data ONTAP Fundamentals课程学习(1)
- 六个建议防止SQL注入式攻击
- 【LKA】国内车道相关数据
- 用费曼学习法学习费曼学习法-读《世界上最好的学习法:费曼学习法》收获
- c# 试图加载格式不正确的程序。 (异常来自 HRESULT:0x8007000B)
- 计算机科学与技术陈梦如,淮南师范学院马克思主义学院文件.doc
- JAVA实现简单的猜数字游戏
- guid备份分区表crc错误_硬盘GPT和MBR分区表转换方法
- CityMaker学习教程10 示例代码的使用Javascript
- 跟ChatGPT,聊聊ChatGPT
- [大话设计模式C++版] 第12章 牛市股票还会亏钱 —— 外观模式
- 如何在深度学习过程中使用预训练的词表征(持续更新ing...)
- p标签内如何让内容换行