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码随便填就好了,点击创建完成

Unity游戏iOS AppStore 内付费接入(In app purchase) - zhanxi1992 - zhanxi1992

 

(2)点击你刚创建好的App,选择 app 内购买项目(In app purchase),事先要设置好收款的信用卡账户,否则系统会提示你去设置。进入App 内购设置项后,点击Create New,选择Consumable (消耗型项目),填写相关信息就好了,ProductID(产品ID)必须唯一,最后添加语言和截屏就完成了,下面我们进入编程阶段。

 

Unity游戏iOS AppStore 内付费接入(In app purchase) - zhanxi1992 - zhanxi1992

 

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 

Unity游戏iOS AppStore 内付费接入(In app purchase) - zhanxi1992 - zhanxi1992

 

 

参考链接:

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章