Unity IAP

One·2022년 5월 24일
1

Unity

목록 보기
6/9
post-thumbnail

회사에서 인앱결제를 구현하고 나서 정리해놓은 글을 옮겨왔으며 Android 기준으로 설명하였다.

인앱 결제 (Unity IAP)

유니티에서는 인앱 결제 기능 구현을 위해 Unity IAP를 지원하고 있다. Unity IAP를 이용하면 iOS, Mac, Google Play, Windows 등 여러 앱스토어에서 인앱 결제 기능을 쉽게 구현할 수 있다. 또한 'Codeless IAP'를 사용하여 코드 작성 없이 인앱 결제 기능을 구현할 수도 있다.

Unity IAP 설정

  • IAP를 사용하기 위해 유니티에서 Window - Services 를 선택한다. 또는 오른쪽 상단에 구름 모양 버튼을 누른다. (Inspector 창 위쪽에 있다.)

  • Services 또는 구름 아이콘을 클릭하면 Inspector 옆에 Services라는 탭이 생긴다. 'Select organization'을 클릭하여 유니티 계정 이름을 선택하주고 create 버튼을 누른다.

  • 유니티에서 제공하는 Servjce 목록이 나옵니다. In-App Purchasing를 누르면 인앱 결제를 활성화 시키는 창이 나오는 데 Enable을 눌러 활성화 시켜준다.

  • 현재 앱이 13세 이하의 어린이를 대상으로 하는지 체크하는 창이 나옵니다. 본인이 만들고 있는 앱이 13세 이하 어린이를 대상으로 한다면 체크, 아니면 체크하지 않고 Continue 버튼을 누른다.

  • Unity IAP 패키지를 프로젝트로 가져오기 위해 Import를 클릭한다.

  • 패키지를 Import 하면 Plugins 폴더가 프로젝트에 추가된다. Plugins 폴더에는 Unity IAP를 사용하는 데 필요한 UnityPurchasing Asset이 포함되어 있다.

  • Import가 완료되면 Services 목록에 In-App Purchasing이 ON으로 바뀌어 있는 것을 볼 수 있다.

인앱 상품 추가

  • 앱 정보 - 인앱 상품 - 관리되는 제품 또는 구독을 클릭하고 원하는 상품을 추가한다.

관리되는 제품 만들기

  • '관리되는 제품 만들기' 버튼을 누른다.

  • 제품 ID, 이름, 설명, 가격 등을 입력하고 상태를 활성으로 바꿔준 후 저장을 눌러 저장한다.

  • 저장을 하고 다시 관리되는 제품 화면으로 가면 상품이 추가된 것을 볼 수 있다. (상태에 활성으로 되어있어야 한다.)

구독 만들기

  • '구독 만들기' 버튼을 누릅니다.

  • 제품 ID, 이름, 설명, 가격 등을 입력해 준 후 저장을 눌러 저장합니다.

  • 저장이 완료되면 아래에 활성화 버튼이 생깁니다. 활성화 버튼을 눌러 활성화시켜 줍니다.

  • 다시 구독 화면으로 돌아가면 생성한 구독을 확인할 수 있습니다. (상태에 활성으로 되어있어야 합니다.)

        

코드 작성

  • Unity IAP를 사용하기 위해 project에 IAPManager 스크립트를 생성한다.

  • UnityEngine.Purchasing 네임스페이스를 추가해주고 IAPManger 클래스가 IStoreListener를 상속받도록 추가해줍니다.
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Purchasing;

public class IAPManager : MonoBehaviour, IStoreListener {

}

IStoreListener 는 구매 관련 이벤트들을 어플리케이션에 알려주는 기능을 가지고 있다. 인터페이스이기 때문에 IStoreListener에 정의된 함수들을 구현해야 한다.

IStoreListener 내에 정의된 함수

  • OnInitialized : Unity IAP가 모든 제품 메타 데이터를 검색하여 구매할 준비가되면 호출된다.

  • OnInitializeFailed : Unity IAP가 초기화에 실패할 경우 호출된다.

  • OnPurchaseFailed : 구매가 실패하면 호출된다.

  • ProcessPurchase : 구매가 성공하면 호출된다.

  • 소모품, 비소모품, 구독 상품 ID를 가지는 string 변수를 만들고 인앱 상품에 등록할때 입력했던 상품의 ID 값을 저장할 변수를 만든다.

public const string productIDConsumable = "consumable";  
public const string productIDNonConsumable = "nonconsumable";
public const string productIDSubscription = "subscription";

//Android 상품 ID
private const string _Android_ConsumableId = "com.studio.app.consumable.android";
private const string _Android_NonconsumableId = "com.studio.app.nonconsumable.android";
private const string _Android_SubscriptionId = "com.studio.app.subscription.android";

//iOS 상품 ID
private const string _IOS_ConsumableId = "com.studio.app.consumable.ios";
private const string _IOS_NonconsumableId = "com.studio.app.nonconsumable.ios";
private const string _IOS_SubscriptionId = "com.studio.app.subscription.ios";
  • Unity IAP 초기화 함수를 만든다.
private void InitUnityIAP() {
    var builder = ConfigurationBuilder.Instance(StandardPurchasingModule.Instance());

    builder.AddProduct(productIDConsumable, ProductType.Consumable, new IDs(){
            {_Android_ConsumableId, GooglePlay.Name },
						{_IOS_ConsumableId, AppleAppStore.Name}
        }
    );

    builder.AddProduct(productIDNonConsumable, ProductType.NonConsumable, new IDs(){
            {_Android_NonconsumableId, GooglePlay.Name },
						{_IOS_NonconsumableId, AppleAppStore.Name}
       }
   );

    builder.AddProduct(productIDSubscription, ProductType.Subscription, new IDs(){
            {_Android_SubscriptionId, GooglePlay.Name },
						{_IOS_SubscriptionId, AppleAppStore.Name}
       }
   );

    UnityPurchasing.Initialize(this, builder);
}

AddProduct 함수를 이용하여 상품을 추가한다. 위에서 선언한 productID와 상품 유형(consumable, nonconsumable, subscription)을 IDs로 상품을 추가한다.

IDs에는 Google Play Console에서 인앱 상품을 생성할때 입력 했던 ID 값과 Store 이름을 넣어준다. (Apple App Store라면 ID에 App Store Connect에서 생성한 인앱 상품 ID를 입력하고 Store이름에 AppleAppStore.Name으로 넣어준다.)

  • ISotreController, IExtensionProvider 객체를 선언해줍니다.
    private IStoreController storeController; 
    private IExtensionProvider storeExtensionProvider; 
    IStoreController는 구매 과정을 제어하는 함수를 제공하며 IExtensionProvider는 여러 플랫폼을 위한 확장 처리를 제공한다.
  • IStoreListener 인터페이스에 정의되어 있는 4개의 함수를 구현해준다.
    public void OnInitialized(IStoreController controller, IExtensionProvider extension) {
        Debug.Log("유니티 IAP 초기화 성공");
        storeController = controller;
        storeExtensionProvider = extension;
    }
    public void OnInitializeFailed(InitializationFailureReason error) {
        Debug.LogError($"유니티 IAP 초기화 실패 {error}");
    }
    public void OnPurchaseFailed(Product product, PurchaseFailureReason reason) {
        Debug.LogWarning($"구매 실패 - {product.definition.id}, {reason}");
    }
    public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs args) {
        Debug.Log($"구매 성공 - ID : {args.purchasedProduct.definition.id}");
    
        if (args.purchasedProduct.definition.id == ProductGold) {
            Debug.Log("소모품 구매 완료");
        } else if (args.purchasedProduct.definition.id == ProductCharacterSkin) {
            Debug.Log("비 소모품 구매 완료");
        } else if (args.purchasedProduct.definition.id == ProductSubscription) {
            Debug.Log("구독 서비스 구매 완료");
        }
    
        return PurchaseProcessingResult.Complete;
    }
  • 구매하기 버튼을 눌렀을 때 실행할 함수를 작성한다.
    public void Purchase(string productId) {
            if (!IsInitialized) {
                return;
            }
    
            var product = storeController.products.WithID(productId);
            if (product != null && product.availableToPurchase) {
                Debug.Log($"구매 시도 - {product.definition.id}");
                storeController.InitiatePurchase(product);
            } else {
                Debug.Log($"구매 시도 불가 - {productId}");
            }
        }
  • IAPManager 클래스를 싱글톤으로 만들기 위해 다음과 같은 코드를 추가한다. (이 예제에서는 편의상 싱글톤으로 했다.)
    private static IAPManager instance;
    public static IAPManager Instance {
        get {
            if (instance != null) {
                return instance;
            }
    
            instance = FindObjectOfType<IAPManager>();
    
            if (instance == null) {
                instance = new GameObject("IAP Manager").AddComponent<IAPManager>();
            }
    
            return instance;
        }
    }
  • IAPManager가 생성될 때 초기화를 해주기 위해 Awake 함수에서 InitUnityIAP를 호출한다.
    private void Awake() {
        if (instance != null && instance != this) {
            Destroy(gameObject);
            return;
        }
    
        DontDestroyOnLoad(gameObject);
        InitUnityIAP();
    }

인앱 결제 테스트

  • 소모품, 비 소모품, 구독 결제를 위한 버튼 3개를 만든다.

  • 버튼에 붙여 줄 PurchaseButton 스크립트를 만든다.

  • PurchaseButton 스크립트를 아래와 같이 작성한다. 각 버튼마다 해당하는 상품 ID를 저장하는 변수가 있으며 버튼 클릭시 IAPManager에 해당 상품 ID를 전달한다.

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class PurchaseButton : MonoBehaviour
    {
        public string targetProductId;
    
        public void HandleClick() {        
            IAPManager.Instance.Purchase(targetProductId);
        }
    }
  • 만들어 둔 버튼에 PurchaseButton Script를 붙이고 Target Product
    id에 해당 상품의 ID를 입력한다. 그리고 Button - On Click()에 이벤트를 추가하고 OnClickPurchase() 함수를 지정한다.

  • 프로젝트를 실행하고 각 버튼을 누르면 다음과 같은 결과를 얻을 수 있다.

    • Consumable 버튼을 클릭했을 때

    • Nonconsumable 버튼을 클릭했을 때

    • Subscription 버튼을 클릭했을 때

모바일에서 테스트

모바일에서 테스트 구매를 하고 싶을 때는 개발 도구 - 내부 앱 공유 - 테스트 참여 대상 관리에서 앱 테스트 참여 대상에 목록을 추가하고 Google Play Console - 설정 - 개발자 계정 - 계정 세부정보 - 라이선스 테스트에 테스트 권한이 있는 Gmail 계정에 계정을 추가해야 한다.

테스트 계정을 등록하면 아래와 같이 인앱 결제를 테스트해 볼 수 있다.

구매 영수증

  • Unity IAP는 구매 영수증을 키와 값을 포함한 JSON 해시로 제공합니다.

영수증 검증

  • Unity IAP는 어플리케이션에서 암호화 키를 난독 처리할 수 있는 툴을 제공한다. 이 툴은 키를 난독 처리하여 사용자가 액세스하기 어렵게 만든다.
  • CrossPlatformValidator 클래스를 사용하여 Google Play 또는 Apple 스토어의 구매 영수증의 유효성을 검사할 수 있다.
  • Window - Unity IAP - IAP Receipt Validation Obfuscator를 클릭하면 다음과 같은 창이 나온다.

  • 설명에 나온 것처럼 개발 도구 - 서비스 및 API - 라이선스 및 인앱 결제에서 라이선스 키를 복사하여 붙여 넣고 Obfuscate Google Play License Key 버튼을 누른다.

  • 위에서 작성한 IAPManager에 다음과 같은 코드를 작성한다. Unity IAP가 초기화에 성공했을 때 이미 구매한 상품이 있으면 해당 상품의 Product ID, PUrchase date, Transaction ID, Purchase token를 출력하도록 한다.

    public void OnInitialized(IStoreController controller, IExtensionProvider extension) {
    ....
    ....
    	validator = new CrossPlatformValidator(GooglePlayTangle.Data(),
    	   AppleTangle.Data(), Application.identifier);
    	
    	var products = storeController.products.all;
    	foreach(var product in products) {
    	    if (product.hasReceipt) {
    	        var result = validator.Validate(product.receipt);
    	
    	        foreach (IPurchaseReceipt productReceipt in result) {
    	            //앱스토어의 경우 GooglePlayReceipt를 AppleInAppPurchaseReceipt로 바꾸면 됩니다.
    	            GooglePlayReceipt googlePlayReceipt = productReceipt as GooglePlayReceipt;
    	            if (null != googlePlayReceipt) {
    	                Debug.Log($"Product ID : {googlePlayReceipt.productID}");
    	                Debug.Log($"Purchase date : {googlePlayReceipt.purchaseDate.ToLocalTime()}");
    	                Debug.Log($"Transaction ID : {googlePlayReceipt.transactionID}");
    	                Debug.Log($"Purchase token : {googlePlayReceipt.purchaseToken}");
    	            }
    	        }
    	    }
    	}
    }
  • 앱을 빌드하고 테스트를 하면 로그캣을 통해 다음과 같은 결과를 얻을 수 있다.

참조

profile
Unity Developer

1개의 댓글

comment-user-thumbnail
2024년 7월 2일

우왕 정보 감사합니다!
구글 UI 진짜 오랜만에 보네요. 대략 2010년도 중후반 쯤이었나... 추억~

답글 달기