前阵子忙着赶项目没什么时间做总结,前两个星期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
1 // AppStorePayForUnity.h
2 #ifndef _AppStorePayForUnity_h
3 #define _AppStorePayForUnity_h
4
5 #import
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
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