회사에서 인앱결제를 구현하고 나서 정리해놓은 글을 옮겨왔으며 Android 기준으로 설명하였다.
유니티에서는 인앱 결제 기능 구현을 위해 Unity IAP를 지원하고 있다. Unity IAP를 이용하면 iOS, Mac, Google Play, Windows 등 여러 앱스토어에서 인앱 결제 기능을 쉽게 구현할 수 있다. 또한 'Codeless IAP'를 사용하여 코드 작성 없이 인앱 결제 기능을 구현할 수도 있다.
Services 또는 구름 아이콘을 클릭하면 Inspector 옆에 Services라는 탭이 생긴다. 'Select organization'을 클릭하여 유니티 계정 이름을 선택하주고 create 버튼을 누른다.
Import가 완료되면 Services 목록에 In-App Purchasing이 ON으로 바뀌어 있는 것을 볼 수 있다.
관리되는 제품 만들기
'관리되는 제품 만들기' 버튼을 누른다.
제품 ID, 이름, 설명, 가격 등을 입력하고 상태를 활성으로 바꿔준 후 저장을 눌러 저장한다.
구독 만들기
'구독 만들기' 버튼을 누릅니다.
제품 ID, 이름, 설명, 가격 등을 입력해 준 후 저장을 눌러 저장합니다.
저장이 완료되면 아래에 활성화 버튼이 생깁니다. 활성화 버튼을 눌러 활성화시켜 줍니다.
다시 구독 화면으로 돌아가면 생성한 구독을 확인할 수 있습니다. (상태에 활성으로 되어있어야 합니다.)
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";
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으로 넣어준다.)
private IStoreController storeController;
private IExtensionProvider storeExtensionProvider;
IStoreController는 구매 과정을 제어하는 함수를 제공하며 IExtensionProvider는 여러 플랫폼을 위한 확장 처리를 제공한다.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}");
}
}
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;
}
}
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 계정에 계정을 추가해야 한다.
테스트 계정을 등록하면 아래와 같이 인앱 결제를 테스트해 볼 수 있다.
위에서 작성한 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}");
}
}
}
}
}
앱을 빌드하고 테스트를 하면 로그캣을 통해 다음과 같은 결과를 얻을 수 있다.
우왕 정보 감사합니다!
구글 UI 진짜 오랜만에 보네요. 대략 2010년도 중후반 쯤이었나... 추억~