Unity 整合原生IOS IAP功能
IAP配置過程
第一步:需要配置銀行、稅務等資訊。
第二步:在App Store Connect上配置App的購買項內容。
第三步:程式碼
第四步:Test,需要使用沙盒測試,App Store Connect上的使用者與賬號中新增。
中文參考
這裡還是推薦英文參考:以下是App Store Help上的描述。每一步都很詳細了,一些中文的參考都不是很全。
https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/StoreKitGuide/Chapters/ShowUI.html#//apple_ref/doc/uid/TP40008267-CH3-SW9
程式碼流程
第一步:初始化,
// init 的時候註冊回撥
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
目前是放在了StartUnity方法中。【需要優化】
// dealloc 的時候刪除回撥
[[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
第二步:獲取Products列表。這個不需要Apple ID登入。這裡有兩個分支,Debug開發環境和生產環境。目前只要是Debug執行的話,就可以獲取到。
第三步:購買。
第四步:Restore已經購買的內容,主要是發生在App重新安裝時,目前的方案是通過一個本地持久化的標誌位,來判斷是否Restore已購買的商品。但如果當前如果沒有登入使用者的,是否還需要儲存AppleID呢?
https://developer.apple.com/documentation/storekit/skpaymentqueue/1506123-restorecompletedtransaction
func restoreCompletedTransactions()
無引數方法,這個是否應該是呼叫後會彈出UI,選擇Apple ID呢?
restoreCompletedTransactions(withApplicationUsername:)
有引數版本,AppUserName 就是AppleID嗎?
// 3. 點選恢復購買的時候呼叫
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
// 4. updatedTransactions中實現 SKPaymentTransactionStateRestored 狀態
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
transaction.transactionIdentifier, transaction.payment.productIdentifier
這裡需要注意的是這兩個ID的區別。第二個是真正的ProductID,第一個只是一個交易ID,每一次交易都會產生一個ID。
//5.實現paymentQueueRestoreCompletedTransactionsFinished回撥(恢復成功時先呼叫4,後呼叫5,)
- (void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue
//6.實現 restoreCompletedTransactionsFailedWithError(恢復錯誤呼叫)
- (void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError*)error
小坑:
目前以上程式碼是以把目前直接放到以下目錄的方式,這裡再Build IOS工程會在XCode裡生成複本檔案。如果需要修改要修改以下目錄下的內容,而不是複本,否則每次新Build就會覆蓋新的修改內容。
全部程式碼如下:
//
// IAPLib.m
// Unity-iPhone
//
// Created by benny on 2018/10/24.
//
#import "IAPLib.h"
@implementation IAPLib
{
}
- (void) registerHandler{
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
}
- (void)validateProductIdentifiers{
NSSet *set001 = [[NSSet alloc] initWithObjects:@"1", @"2", @"3", @"4", nil];
SKProductsRequest *productsRequest = [[SKProductsRequest alloc]
initWithProductIdentifiers:set001];
// Keep a strong reference to the request.
self.request = productsRequest;
productsRequest.delegate = self;
[productsRequest start];
}
// SKProductsRequestDelegate protocol method
//- (void)productsRequest:(SKProductsRequest *)
//requesdidReceiveResponse : (SKProductsResponse *)response {
// self.products = response.products;
// for (NSString *invalidIdentifier in response.invalidProductIdentifiers) {
// // Handle any invalid product identifiers.
// }
// //[self displayStoreUI]; // Custom method
//}
- (void)productsRequest:(nonnull SKProductsRequest *)request didReceiveResponse:(nonnull SKProductsResponse *)response {
self.products = response.products;
NSLog(@"product count is %u", [self.products count]);
//for testmessage
UnitySendMessage("CtrlUI", "OnTest", "111111222333");
// for (NSString *invalidIdentifier in response.invalidProductIdentifiers) {
// // Handle any invalid product identifiers.
// }
// if ([self.products count] > 0){
// SKProduct* product = self.products[0];
// NSLog(@"Buying %@... + %@ ", product.productIdentifier, product.localizedTitle);
//
// }
// [self buyProduct];
}
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error {
NSLog(@"Failed to load list of products.");
self.request = nil;
}
- (void)buyProduct{
SKProduct* product = self.products[0];
NSLog(@"Buying %@... + %@ ", product.productIdentifier, product.localizedTitle);
SKPayment * payment = [SKPayment paymentWithProduct:product];
[[SKPaymentQueue defaultQueue] addPayment:payment];
}
//restore func
- (void) RestoreProducts{
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
}
- (void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue{
NSLog(@"Buying paymentQueueRestoreCompletedTransactionsFinished");
}
- (void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error{
NSLog(@"Buying restoreCompletedTransactionsFailedWithError %@", error.localizedDescription);
}
//handle message
- (void)paymentQueue:(nonnull SKPaymentQueue *)queue updatedTransactions:(nonnull NSArray<SKPaymentTransaction *> *)transactions {
for (SKPaymentTransaction *transaction in transactions) {
switch (transaction.transactionState) {
// Call the appropriate custom method for the transaction state.
case SKPaymentTransactionStatePurchasing:
//[self showTransactionAsInProgress:transaction deferred:NO];
break;
case SKPaymentTransactionStateDeferred:
//[self showTransactionAsInProgress:transaction deferred:YES];
break;
case SKPaymentTransactionStateFailed:
[self failedTransaction:transaction];
break;
case SKPaymentTransactionStatePurchased:
[self completeTransaction:transaction];
break;
case SKPaymentTransactionStateRestored:
[self restoreTransaction:transaction];
break;
default:
// For debugging
NSLog(@"Unexpected transaction state %@", @(transaction.transactionState));
break;
}
}
}
#pragma mark - Callbacks
- (void)completeTransaction:(SKPaymentTransaction *)transaction {
NSLog(@"complete transaction...");
// TODO: verify transaction receipts, provide contents
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
UnitySendMessage("CtrlUI", "OnBuyEnd", "End");
}
- (void)restoreTransaction:(SKPaymentTransaction *)transaction {
NSLog(@"restore transaction... transaction id is %@ and the product id is %@", transaction.transactionIdentifier, transaction.payment.productIdentifier);
// TODO: verify transaction receipts, provide contents
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
UnitySendMessage("CtrlUI", "OnRestoreProduct", [transaction.payment.productIdentifier cStringUsingEncoding:NSASCIIStringEncoding]);
}
- (void)failedTransaction:(SKPaymentTransaction *)transaction {
NSLog(@"failed transaction...");
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
@end
#import <Foundation/Foundation.h>
#import <StoreKit/StoreKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface IAPLib : NSObject<SKProductsRequestDelegate, SKPaymentTransactionObserver>
@property (nonatomic,strong) SKProductsRequest * request;
@property (nonatomic,strong) NSArray<SKProduct*> * products;
- (void) registerHandler;
- (void)validateProductIdentifiers;
- (void)buyProduct;
- (void) RestoreProducts;
@end
NS_ASSUME_NONNULL_END
//
// StoreLib.m
// Unity-iPhone
//
// Created by benny on 2018/10/24.
//
#import <Foundation/Foundation.h>
#import "IAPLib.h"
IAPLib* iapmgr;
extern "C" void RequestProducts()
{
// strdup(const char *__s1) 複製mHost字串,通過Malloc()進行空間分配
//return strdup(mHost);
[iapmgr validateProductIdentifiers];
}
extern "C" void BuyProducts()
{
// strdup(const char *__s1) 複製mHost字串,通過Malloc()進行空間分配
//return strdup(mHost);
NSLog(@"init buyProduct");
[iapmgr buyProduct];
}
extern "C" void RestoreProducts()
{
// strdup(const char *__s1) 複製mHost字串,通過Malloc()進行空間分配
//return strdup(mHost);
NSLog(@"restore buyProduct");
[iapmgr RestoreProducts];
}
extern void initIAP(){
NSLog(@"init IAP");
iapmgr = [[IAPLib alloc] init];
if (iapmgr != nil){
[iapmgr registerHandler];
[iapmgr validateProductIdentifiers];
}
}
最後是有關沙盒測試需要注意的問題:
1. 不能是正常的AppleID
2. 已經註冊的Test ID不能再被其它App裡註冊了,這種是否可以直接使用?