前陣子忙着趕項目沒什麼時間做總結,前兩個星期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