Unity3D Integration iOS In App Purchase

 cocos2d-x에 iOS 인앱 연동 정리했던것과 내용은 거의 같습니다. 안드로이드 인앱 빌링이 V3로 되면서 많이 바뀐것에 비하면 iOS는 바뀌지 않은 것 같네요. 사실 cocos2d-x에서 작업한 내용을 그대로 가져와 유니티3d에 맞게 조금 수정해서하니 잘 되길래 iOS 인앱 관련 최신 레퍼런스는 살펴보지도 못했네요.

  1. iOS 개발 인증서 만들어 요청 및 설치하기
  2. iOS 디바이스에서 테스트하기 위한 디바이스 등록 및 앱 ID, 개발 프로비저닝 프로파일 생성
  3. 애플 앱스토어에 앱 등록해보기 1. Prepare for Upload ~ Waiting for Uplod
  4. iOS 인앱 결제 코드 구현 전 웹 설정 작업
 코드 작업전에 위와 같은 선행작업들이 있는데 기존에 정리했던 것을 링크로 대체합니다.


 위 그림과 같이 iOS 플러그인을 만듭니다. 아래 소스 부분은 몇몇 유니티3D 관련 부분 빼고는 기존 cocos2d-x에서 했던 것과 비슷합니다.


// iOSInAppPlugin.h

#import <StoreKit/StoreKit.h>

@interface iOSInAppPlugin : NSObject<
///< 상품 정보를 얻어올 쓰는 딜리게이트
SKProductsRequestDelegate,
///< 상품 구매 관련 옵저버
SKPaymentTransactionObserver>

+ (iOSInAppPlugin*) sharediOSInAppPlugin;
- (BOOL) initInApp;
- (void) requestProductData:(NSString*)strProductId;

- (void) completeTransaction:(SKPaymentTransaction*)transaction;
- (void) restoreTransaction:(SKPaymentTransaction*)transaction;
- (void) failedTransaction:(SKPaymentTransaction*)transaction;

- (void) restoreCompletedTransactions;

@end



// iOSInAppPlugin.mm

#import "iOSInAppPlugin.h"

extern "C"
{
    void iOSInAppInit()
    {
        [[iOSInAppPlugin sharediOSInAppPlugin] initInApp];
    }
    void iOSBuyItem(const char* pszProductId)
    {
        NSString* strProductId = [NSString stringWithUTF8String:pszProductId];
        [[iOSInAppPlugin sharediOSInAppPlugin] requestProductData:strProductId];
    }
    void iOSRestoreCompletedTransactions()
    {
        [[iOSInAppPlugin sharediOSInAppPlugin] restoreCompletedTransactions];
    }
}

@implementation iOSInAppPlugin

+ (iOSInAppPlugin*) sharediOSInAppPlugin
{
    static iOSInAppPlugin* pInstance;
    if(pInstance == NULL)
    {
        pInstance = [[iOSInAppPlugin alloc] init];
    }
    return pInstance;
}

- (BOOL) initInApp
{
    ///< 인앱 결제 시스템을 사용 가능한지 체크
    if( [SKPaymentQueue canMakePayments] == NO )
        return NO;
    
    ///< Product 결제 진행에 필요한 딜리게이트 등록
    [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
    
    NSLog(@"InAppPurchase init OK");
    return true;
}

///< 아이템 정보 요청
- (void) requestProductData:(NSString*)strProductId
{
    ///< iTunes Connect 설정한 Product ID
    NSSet* productIdentifiers = [NSSet setWithObject:strProductId];
    SKProductsRequest* request = [[SKProductsRequest alloc] initWithProductIdentifiers:productIdentifiers];
    request.delegate = self;
    [request start];
    NSLog(@"requestProductData %@", strProductId);
}

///< 아이템 정보 요청 결과 callback
- (void) productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
{
    NSLog( @"InAppPurchase didReceiveResponse" );
    for( SKProduct* product in response.products )
    {
        if( product != nil )
        {
            NSLog(@"InAppPurchase Product title: %@", product.localizedTitle);
            NSLog(@"InAppPurchase Product description: %@", product.localizedDescription);
            NSLog(@"InAppPurchase Product price: %@", product.price);
            //product.priceLocale
            NSLog(@"InAppPurchase Product id: %@", product.productIdentifier);
            
            ///< 구매 요청
            
            SKPayment* payment = [SKPayment paymentWithProduct:product];
            //payment.quantity = 10;
            [[SKPaymentQueue defaultQueue] addPayment:payment];
        }
    }
    
    [request release];
    
    for (NSString *invalidProductId in response.invalidProductIdentifiers)
    {
        NSLog(@"InAppPurchase Invalid product id: %@", invalidProductId);
    }
}

///< 새로운 거래가 발생하거나 갱신될 호출된다.
- (void) paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
    for (SKPaymentTransaction *transaction in transactions)
    {
        switch (transaction.transactionState)
        {
                ///< 서버에 거래 처리중
            case SKPaymentTransactionStatePurchasing:
                NSLog(@"InAppPurchase SKPaymentTransactionStatePurchasing");
                break;
                ///< 구매 완료
            case SKPaymentTransactionStatePurchased:
                [self completeTransaction:transaction];
                break;
                ///< 거래 실패 또는 취소
            case SKPaymentTransactionStateFailed:
                [self failedTransaction:transaction];
                break;
                ///< 재구매
            case SKPaymentTransactionStateRestored:
                [self restoreTransaction:transaction];
                break;
        }
    }
}

- (void) completeTransaction:(SKPaymentTransaction *)transaction
{
    NSLog(@"InAppPurchase completeTransaction");
    NSLog(@"InAppPurchase Transaction Identifier : %@", transaction.transactionIdentifier );
    NSLog(@"InAppPurchase Transaction Data : %@", transaction.transactionDate );
    ///< 구매 완료 아이템 인벤등 게임쪽 처리 진행
    /* 빌트 인 모델
    const char* pszProductId = [[[transaction payment] productIdentifier] UTF8String];
    UnitySendMessage("iOSManager", "ResultBuyItem", pszProductId);
     */
   
    NSString* strReceipt = [[NSString alloc] initWithBytes:transaction.transactionReceipt.bytes length:transaction.transactionReceipt.length encoding:NSUTF8StringEncoding];
    
    UnitySendMessage("iOSManager", "ResultBuyItem", [strReceipt UTF8String]);

    // Remove the transaction from the payment queue.
    [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}

- (void) restoreTransaction:(SKPaymentTransaction *)transaction
{
    NSLog(@"InAppPurchase restoreTransaction");
    const char* pszRestoreProductId = [transaction.originalTransaction.payment.productIdentifier UTF8String];
    UnitySendMessage("iOSManager", "ResultRestoreItem", pszRestoreProductId);
    [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}

- (void) failedTransaction:(SKPaymentTransaction *)transaction
{
    NSLog(@"InAppPurchase failedTransaction.");
    const char* pszResult = 0;
    if( transaction.error.code != SKErrorPaymentCancelled )
    {
        pszResult = "faileIAP";
        NSLog(@"InAppPurchase failedTransaction SKErrorDomain - %d", transaction.error.code );
    }
    else
    {
        pszResult = "cancelIAP";
        NSLog(@"InAppPurchase failedTransaction SKErrorPaymentCancelled");
    }
    UnitySendMessage("iOSManager", "ResultBuyItem", pszResult);
    [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}

// 비소모성 아이템 복원 요청
- (void) restoreCompletedTransactions
{
    [[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
}

@end

 completeTransaction 에서 빌트인 모델이라고 주석된 부분은 기존에 cocos2d-x에 정리했던 것과 같은 클라이언트만 가지고 인앱을 처리하는 부분이고 서버와 인앱 영수증 처리를 하려면 transaction.transactionReceipt.bytes 를 Base64 Encode등의 처리를 거쳐서 애플 서버와 인증 확인을 해줘야합니다. 관련 소스는 몇몇 부분이 비워 있긴 하지만 애플이 만든게 있으니 링크를 통해 확인하시기 바랍니다. 추후 서버측 작업을 제가 하게되면 그때 또 정리하기로 하고 지금은 자체 서버에서 받아 처리하고 있어서 일단 유니티3D에 넘겨 주기만 하고 있습니다.


// iOSManager.cs

using UnityEngine;

using System.Runtime.InteropServices;

public class iOSManager : MonoBehaviour 
{
static iOSManager _instance;
private string strLog = "Unity3D iOS In App Purchase Sample";
public string strPostMsg = string.Empty;
[DllImport("__Internal")]
private static extern void iOSInAppInit();
[DllImport("__Internal")]
private static extern void iOSBuyItem(string strProductId);
[DllImport("__Internal")]
private static extern void iOSRestoreCompletedTransactions();
public static iOSManager GetInstance()
{
if( _instance == null )
{
_instance = new GameObject("iOSManager").AddComponent<iOSManager>();
}
return _instance;
}
public void InAppInit()
{
iOSInAppInit();
}

public void BuyItem(string strProductId)
{
iOSBuyItem(strProductId);
}
public void ResultBuyItem(string strResult)
{
// strResult is transaction.transactionReceipt or faileIAP or cancelIAP.
SetLog("ResultBuyItem " + strResult);
}
public void RestoreCompletedTransactions()
{
iOSRestoreCompletedTransactions();
}
public void ResultRestoreItem(string strRestoreItemId)
{
SetLog("ResultRestoreItem " + strRestoreItemId);
}

public void SetLog(string _strLog)
{
strLog = _strLog;
}
public string GetLog()
{
return strLog;
}
}


// TestGUI.cs

void OnGUI()
{
float fYpos = 0;
GUI.Label(new Rect(0, fYpos, 400, 50), iOSManager.GetInstance().GetLog());
fYpos += 50;
if (GUI.Button (new Rect(0, fYpos, 100, 50), "InApp Init") == true)
{
iOSManager.GetInstance().InAppInit();
}
fYpos += 50;
if (GUI.Button (new Rect(0, fYpos, 100, 50), "TestItem1") == true)
{
iOSManager.GetInstance().BuyItem("inapptestitem1");
}
fYpos += 50;
if (GUI.Button (new Rect(0, fYpos, 100, 50), "Restore") == true)
{
iOSManager.GetInstance().RestoreCompletedTransactions();
}
}



 인앱 초기화 후 아이템 구매 시도중인 화면입니다.

이 블로그의 인기 게시물

CMake Windows에 설치하기

Unity3D 안드로이드 Keystore 생성하기

Unity3D iOS Plugin 만들어 연동하기