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

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