Unity에서 원스토어 인앱결제 사용하기
Last updated
Last updated
Unity 개발환경에서 원스토어 인앱결제를 간편하게 적용 할 수 있도록 원스토어 Unity Plugin을 제공합니다. 이 문서에서는 프로젝트를 설정하여 Unity 플러그인을 사용하는 방법과 게임에 결제 기능을 구현하는 방법을 설명합니다.
Unity
2020.3.48f
Java SDK (Java 11)
Purchase: v19.01.00
1. Unity용 결제 플러그인을 Github에서 다운로드 받습니다.
2. Unity 메뉴 바에서 Assets > Import Package > Custom Package 를 클릭합니다.
3. 다운로드한 위치를 찾아 .unitypackage
파일을 선택합니다.
4. Import Unity Package 대화상자에서 모든 에셋을 선택한 상태로 두고 Import 를 클릭합니다.
Assets/Scripts/Purchase
GaaIapCallManager.cs
Unity에서 인앱 결제 SDK를 활용하여 API를 호출하는 역할을 담당합니다.
GaaIapCallbackManager.cs
API호출에 대한 응답을 받는 역할을 담당합니다.
JNI를 통해 데이터를 주고받는 객체이므로 수정 시 정상적인 응답을 받지 못할 수 있습니다.
GaaIapResultResponse.cs
GaaIapCallbackManager
에서 받은 응답 데이터를 가공하는 역할을 담당합니다.
개발사에 맞게 수정 가능한 파일입니다.
GaaPurchaseResponse.cs
Unity 인앱 결제 플러그인에서 사용하는 Value Object 가 정의된 파일입니다.
AndroidNative.cs
예제에서 AlertDialog
또는 Toast
를 띄우기 위한 유틸 파일입니다.
Assets/Plugins/Android
iap_sdk-v19.xx.xx.aar
원스토어 인앱 결제 Java 라이브러리 파일입니다.
iap_adapter-vx.x.x.aar
Java 라이브러리와 Unity 코드 간에 데이터를 주고받기 위한 브리지 파일입니다.
AndroidManifest.xml
Custom Main Manifest를 선택하여 아래의 설정 값을 추가합니다.
ProxyActivity
와 meta-data
를 추가해주세요.
iap:view_option
은 더 이상 지원하지 않습니다.
AndroidManifest.xml
파일에 <queries>
를 설정 해야합니다.
자세한 내용은 공지사항을 참조하세요.
<queries>
태그를 설정하지 않으면 SDK에서 원스토어 서비스를 찾을 수 없습니다.
SDK가 강제로 글로벌 스토어로 연동되도록 하는 옵션입니다. 이 기능은 SDK v19.01.00 버전부터 적용되었습니다.
배포 빌드 버전에서는 이 옵션을 반드시 삭제해야 합니다.
개발사에서 난독화를 사용중이라면 아래의 package
를 제외시켜 주세요.
개발 단계에서 로그 레벨을 설정하여 SDK의 데이터의 흐름을 좀 더 자세히 노출할 수 있습니다.
GaaIapCallManager
클래스의 생성자에 setLoglevel
설정합니다.
로그 레벨 설정값은 android.util.Log
에 정의된 값을 기반으로 동작합니다.
배포 빌드 버전에서는 보안에 취약할 수 있으니 이 옵션을 삭제해야 합니다.
상수 | 값 |
---|---|
VERBOSE | 2 |
DEBUG | 3 |
INFO (default) | 4 |
WARN | 5 |
ERROR | 6 |
GaaIapCallManager
객체에서 API 호출 후 응답을 받을 GameObject
를 `GaaIapCallbackManager`
란 이름으로 등록하도록 되어 있습니다.
샘플 코드에 나온 것처럼 GaaIapCallbackManager
란 Empty GameObject
를 만들고 아래의 파일을 등록합니다.
GaaIapCallbackManager.cs
GaaIapResultListener.cs
GaaIapCallbackManager GameObject
는 Scene
전환시 메모리가 해제될 수 있으니
게임이 종료될 때까지 유지해야 합니다.
https://docs.unity3d.com/ScriptReference/Object.DontDestroyOnLoad.html
인앱 결제를 요청 하려면 먼저 다음 작업을 통해 원스토어 서비스에 연결해야합니다.
인앱 결제를 연결하기 위해 원스토어 개발자센터에서 제공하는 라이선스 키를 사용하여
GaaIapCallManager.StartConnection()
API통해 연결을 시도합니다.
연결에 의한 응답을 확인하는 방법은 GaaIapResultListener.PurchaseClientStateEvent()
를 통해 GaaPurchaseResponse.IapResult
객체 형태로 전달 됩니다.
IapResult.code
의 값은 GaaPurchaseResponse.ResponseCode
를 통해 확인할 수 있습니다.
연결에 실패 했을 때는 응답코드에 맞는 대응을 해야 합니다.
앱을 종료할 때는 결제 모듈과의 연결을 끊어야 합니다.
개발자센터에서 인앱상품을 등록할때 생성한 인앱상품 ID는 인앱상품 상세정보를 조회할 때 사용됩니다. 인앱상품 상세정보를 조회하려면 QueryProductDetails()
를 호출하세요. 인앱상품 ID(productId) 배열값과 그에 해당하는 상품타입을 지정합니다.
인앱상품 유형은 아래와 같이 두 종류가 존재합니다.
관리형 상품, 월 정액 상품이 있으며, 두 유형을 모두 한번에 요청할 시에는 ProductType.ALL
유형으로 호출합니다.
Product | Enum |
---|---|
관리형 상품 |
|
월 정액 상품 |
|
ProductType.ALL
은 상품 정보 조회하기에서만 사용할 수 있으며, 구매 요청하기, 구매 내역 조회하기에서는 사용할 수 없습니다.
상품정보 조회의 응답 결과값은 GaaIapResultListener
객체의 두 가지 함수 전달됩니다.
ProductDetailsSuccessEvent()
// 성공
ProductDetailsErrorEvent()
// 실패
Java와 Unity 사이의 통신은 UnitySendMessage
를 통해 문자열을 주고 받습니다.
한 번에 전달할 수 있는 문자열의 길이가 제한되어 한 번에 한 상품씩 전달됩니다.
ProductDetailsSuccessEvent
함수에서 int count, int totalCount 의 값이 중요합니다.
상품이 1개인 경우
count = 1, totalCount = 1 // 두 개의 값이 동일합니다.
상품이 n개인 경우
count = 1, totalCount = n 입니다. count 값이 1씩 증가되면 반복 호출되며, 두 값이 동일할 때까지 호출됩니다.
인앱 상품 ID 목록은 개발사의 보안 백엔드 서버에서 관리해야 합니다.
인앱상품 구매를 위해서는 GaaIapCallManager.LaunchPurchaseFlow()
호출합니다.
호출 시에 GaaPurchaseResponse.PurchaseFlowParams
객체를 생성하여 지정합니다. 필수적으로 필요한 값은 인앱상품 ID (productId
) 와 상품 유형(관리형 상품: ProductType.INAPP
, 월정액 상품: ProductType.AUTO
) 입니다.
또한 개발사가 임의로 입력한 devPayload
(최대 200byte)도 넣습니다. 이 값은 결제 후에 데이터의 정합성과 부가 데이터를 확인하기 위해 사용할 수 있습니다.
productName
의 경우는 등록한 인앱상품 ID의 이름이 아닌 결제시점에 사용자에게 보여주는 인앱상품의 이름을 변경하고자 할 때 사용됩니다.
원스토어는 사용자에게 할인 쿠폰, 캐시백 등의 다양한 혜택 프로모션을 진행하고 있습니다.
개발사는 구매 요청 시에 gameUserId
, promotionApplicable
파라미터를 이용하여 앱을 사용하는 유저의 프로모션 참여를 제한하거나 허용할 수 있습니다.
개발사는 앱의 고유한 유저 식별 번호 및 프로모션 참여 여부를 선택하여 전달하고, 원스토어는 이 값을 토대로 사용자의 프로모션 혜택을 적용하게 됩니다.
gameUserId
, promotionApplicable
파라미터는 옵션 값으로 원스토어 사업 부서 담당자와 프로모션에 대해 사전협의가 된 상태에서만 사용하여야 하며, 일반적인 경우에는 값을 보내지 않습니다.
또한, 사전협의가 되어 값을 보낼 경우에도 개인 정보보호를 위해 gameUserId
는 hash
된 고유한 값으로 전송하여야 합니다.
다음은 인앱상품의 구매를 요청하는 예제입니다.
LaunchPurchaseFlow()
API를 호출하면 원스토어 결제 화면을 표시합니다.
그림 1은 원스토어 상품의 결제 화면을 나타냅니다.
결제에 대한 응답은 GaaIapResultListener
객체의 두 가지 함수로 전달됩니다.
PurchaseUpdatedSuccessEvent()
// 성공
PurchaseUpdatedErrorEvent()
// 실패
PurchaseUpdatedSuccessEvent()
함수도 상품 정보 조회하기 와 같은 이유로 int count, int totalCount가 존재 합니다.
결제에 성공하면 사용자가 구매한 구매 ID를 나타내는 고유 식별자인 구매 토큰이 생성됩니다. 앱에서는 구매 토큰을 로컬에 저장하거나, 보안 백엔드 서버에 저장하여 구매를 확인하는 데 사용할 수 있습니다.
관리형 상품의 구매 토큰은 모든 구매 ID 마다 고유하지만, 월정액 상품의 구매 토큰은 갱신되는 동안 동일하게 유지됩니다.
또한 사용자는 영수증 번호가 포함된 거래 영수증을 이메일로 받습니다. 관리형 상품은 구매할 때마다 이메일을 받으며, 월정액 상품은 처음 구매 시 그리고 이후 갱신될 때마다 이메일을 받습니다.
사용자가 구매를 완료하면 앱에서 구매를 처리해야 합니다.
대부분의 경우 앱은 구매 요청하기의 응답 결과인 PurchaseUpdatedSuccessEvent()
또는 구매 내역 조회하기의 응답 결과인 QueryPurchasesSuccessEvent()
를 통해 소비되지 않은 구매 내역을 받습니다.
3일 이내에 구매를 확인(acknowledge
) 또는 소비(consume
)를 하지 않으면 사용자에게 상품이 지급되지 않았다고 판단되어 자동으로 환불됩니다.
관리형 상품(ProductType.INAPP
)은 소비를 하기 전까지는 재 구매할 수 없습니다.
재 구매를 진행할 수 있도록 하기 위해서는 소비를 진행해야 합니다.
상품을 소비하기 위해서는 GaaIapCallManager.Consume()
API를 호출합니다.
소비에 대한 응답은 GaaIapResultListener
객체의 두 가지 함수로 전달됩니다.
ConsumeSuccessEvent()
// 성공
ConsumeErrorEvent()
// 실패
다음은 관리형 상품을 소비하는 예제 입니다.
관리형 상품의 대부분의 활용도는 즉시 소비하는 소비성 상품입니다. 구매 → 소비하기 (consume) → 상품 지급
하지만, 관리형 상품을 아래와 같이 사용한다면 기간제 상품으로도 활용 가능합니다. 구매 → 상품 확인하기 (acknowledge) → 상품 지급 → 일정 기간이 지한 후 → 소비하기 (consume) → 상품 회수
소비 요청이 때로 실패할 수 있으므로 보안 백엔드 서버를 확인하여 각 구매 토큰이 사용되지 않았는지 확인해야 합니다. 그래야 앱이 동일한 구매에 대해 여러 번 자격을 부여하지 않습니다. 또는 자격을 부여하기 전에 성공적인 소비 응답을 받을 때까지 기다릴 수 있습니다.
원스토어 인앱 라이브러리 v19 이상을 사용하는 경우 3일 이내에 구매 확인을 해야합니다. 구매 확인 되지 않으면 상품 결제 금액은 환불됩니다.
소모성 제품인 경우 GaaIapCallManager.Consume()
API를 사용합니다.
소모성 제품인 아닌 경우 GaaIapCallManager.Acknowledge()
API를 사용합니다.
월 정액 상품의 경우에는 최초 결제에 대해서만 구매를 확인(acknowledge)하면 됩니다.
구매 내역의 확인 유무는 PurchaseData
객체내에 isAcknowledged()
API를 통해 확인할 수 있습니다. 구매 확인 전에 이러한 API를 활용하여 이미 구매 확인이 되었는지 파악할 수 있습니다.
상품을 확인하기 위해서는 GaaIapCallManager.Acknowledge()
API를 호출합니다.
소비에 대한 응답은 GaaIapResultListener
객체의 두 가지 함수로 전달됩니다.
AcknowledgeSuccessEvent()
// 성공
AcknowledgeErrorEvent()
// 실패
AcknowledgeSuccessEvent()
API로 전달되는 PurchaseData
는 요청할 당시의 객체 이므로 acknowledgeState
값을 갱신하기 위해서는 구내 내역 조회하기를 통해 데이터를 다시 받아야 합니다.
다음은 월정액 상품에 대한 구매 확인 방법을 나타낸 예제입니다.
GaaIapCallManager.QueryPurchases()
API를 사용하여 소비되지 않은 관리형 상품과 사용중인 월 정액 상품 정보를 가져옵니다.
인앱 결제 초기화 및 연결하기에서 지정했던 public key를 활용하여 SDK 내부에서는 구매 데이터 위변조 여부를 확인하기 위해 서명 확인 작업을 진행하며, 만약 서명 작업이 실패하면 IapResult
의 에러코드(ResponseCode.ERROR_SIGNATURE_VERIFICATION
)가 전달 됩니다. 이 오류가 발생하면 구매 데이터가 위변조 되었을 가능성이 있으므로 구매에 관련된 어뷰징 공격 등이 있지 않았는지 확인해 볼 필요가 있습니다.
사용자가 앱에서 진행한 구매내역을 조회하려면 다음 예제처럼 상품 유형(ProductType
)을 지정해야 합니다.
소비에 대한 응답은 GaaIapResultListener
객체의 두 가지 함수로 전달됩니다.
QueryPurchasesSuccessEvent()
// 성공
QueryPurchasesErrorEvent()
// 실패
QueryPurchasesSuccessEvent()
함수도 상품 정보 조회하기 와 같은 이유로 int count, int totalCount가 존재 합니다.
구매를 처리하는 것만으로는 앱이 모든 구매를 처리하는 것을 보장하기에 충분하지 않습니다. 앱에서 사용자가 구매한 모든 항목을 인식하지 못할 수 있습니다. 앱에서 구매 추적을 놓치거나 구매를 인식하지 못할 수 있는 몇 가지 시나리오는 다음과 같습니다.
구매 중 네트워크 문제: 사용자가 구매를 성공적으로 완료하고 원스토어에서 확인을 받았지만 기기가 구매 알림을 받기 전에 네트워크 연결이 끊어졌을 경우
여러 기기: 사용자는 한 기기에서 항목을 구입한 후 기기를 전환할 때 이 항목이 표시되기를 기대합니다.
이러한 상황을 처리하려면 앱의 Start()
또는 OnApplicationPause()
에서 PurchaseClientImpl.QueryPurchases()
를 호출하여 구매 후 처리에 설명된 대로 모든 구매가 성공적으로 처리되었는지 확인해야 합니다.
월 정액 상품은 최초 구매 후 30일 뒤에 갱신이 이루어지는 상품입니다.
갱신 이전에 상품의 상태를 변경하기 위해서는 GaaIapCallManager.ManageRecurringProduct()
API를 이용하여 월 정액 상품의 상태를 변경할 수 있습니다.
PurchaseData.recurringState
값에 따라 RecurringAction
값을 설정합니다.
상태 정보는 구매 내역 조회하기 에서 얻어온 PurchaseData.recurringState
를 통해 확인할 수 있습니다.
월 정액 상품 상태 변경하기에 대한 응답은 GaaIapResultListener
객체의 두 가지 함수로 전달됩니다.
RecurringSuccessEvent()
// 성공
RecurringErrorEvent()
// 실패
다음은 월정액 상품의 상태를 변경하는 방법을 나타낸 예제 입니다.
RecurringSuccessEvent()
API로 전달되는 PurchaseData
는 요청할 당시의 객체 이므로 recurringState
값을 갱신하기 위해서는 구내 내역 조회하기를 통해 데이터를 다시 받아야 합니다.
원스토어 인앱결제 SDK는 로그인이 되어 있는 상태에서 동작합니다.
이전에 로그인한 이력이 있으면 원스토어에서 자동으로 로그인을 하지만, 로그인이 필요한 경우는 API 요청시에 IapResult.code
가 ResponseCode.RESULT_NEED_LOGIN
으로 전달 됩니다.
RESULT_NEED_LOGIN
이 전달되면 GaaIapCallManager.LaunchLoginFlow()
API를 호출해야 합니다.
응답에 대한 결과는 GaaIapResultListener.LoginFlowEvent()
를 통해 확인할 수 있습니다.
다음은 원스토어 로그인을 요청하는 예제입니다.
원스토어 서비스의 버전이 낮거나 없을 경우 인앱 결제를 이용할 수 없습니다.
GaaIapCallManager.StartConnection()
을 통해 연결을 시도할 때 응답결과로 ResponseCode.RESULT_NEED_UPDATE
가 발생하면 GaaIapCallManager.LaunchUpdateOrInstallFlow()
API를 호출해야 합니다.
응답에 대한 결과는 GaaIapResultListener.UpdateOrInstallFlowEvent()
를 통해 확인할 수 있습니다.
다음은 원스토어 서비스를 설치하는 방법을 나타낸 예제입니다.
인앱 결제 라이브러리 API v6부터는 Server API를 사용하기 위해서는 스토어 구분 코드가 필요합니다.
GaaIapCallManager.GetStoreInfo()
메서드를 호출하면 GaaIapResultListener.StoreInfoEvent()
를 통해 응답을 확인할 수 있습니다.
다음은 스토어 구분 코드를 획득하는 방법을 나타낸 예제입니다.