AndroidManifest.xml 파일에 <queries>를 설정 해야합니다.
자세한 내용은 공지사항을 참조하세요.
<queries> 태그를 설정하지 않으면 SDK에서 원스토어 서비스를 찾을 수 없습니다.
<manifest><!-- if your binary use ONE store's In-app SDK, Please make sure to declare the following query on Androidmanifest.xml. Refer to the notice for more information. https://dev.onestore.net/devpoc/support/news/noticeView.omp?noticeId=32968 --> <queries> <intent> <actionandroid:name="com.onestore.ipc.iap.IapService.ACTION" /> </intent> <intent> <actionandroid:name="android.intent.action.VIEW" /> <dataandroid:scheme="onestore" /> </intent> </queries> ... <application> ... </application></manifest>
글로벌 스토어 배포를 위한 테스트 옵션 설정
SDK가 강제로 글로벌 스토어로 연동되도록 하는 옵션입니다.
이 기능은 SDK v21.01.00 버전부터 적용되었습니다.
<manifest> ... <application> <activity> ... </activity> ...<!-- Options for in-app testing on your global store --> <meta-dataandroid:name="onestore:dev_option"android:value="global" /> </application></manifest>
배포 빌드 버전에서는 이 옵션을 반드시 삭제해야 합니다.
인앱 라이브러리 적용하기
로그레벨 설정
개발 단계에서 로그 레벨을 설정하여 SDK의 데이터의 흐름을 좀 더 자세히 노출할 수 있습니다.
android.util.Log에 정의된 값을 기반으로 동작합니다.
/** * Set the log level.<br/> * {@link Log#VERBOSE}, {@link Log#DEBUG}, {@link Log#INFO}, * {@link Log#WARN}, {@link Log#ERROR} * @param level int */com.gaa.sdk.base.Logger.setLogLevel(2)
배포 빌드 버전에서는 보안에 취약할 수 있으니 이 옵션을 삭제해야 합니다.
오류 처리
원스토어 결제 라이브러리는 IapResult 형식으로 오류를 반환합니다. IapResult에는 앱에서 발생할 수 있는 결제 관련 오류를 분류하는 ResponseCode가 포함되어 있습니다. 예를 들어 RESULT_NEED_LOGIN 이나 RESULT_NEED_UPDATE 같은 오류 코드가 수신되면 앱에서 그에 맞는 처리를 해야 합니다.
원스토어 로그인 하기
GaaSignInClient 초기화
GaaSignInClient는 원스토어 로그인을 하기 위한 라이브러리입니다.
getClient() 통해 인스턴스를 생성합니다.
val signInClient = GaaSignInClient.getClient(activity)
PurchaseClient는 원스토어 결제 라이브러리와 앱 간의 통신을 위한 기본 인터페이스입니다.
단일 이벤트에 관한 여러 개의 PurchasesUpdatedListener 콜백이 발생하는 상황을 피할 수 있도록 PurchaseClient 연결을 열어 두는 것을 권장합니다.
PurchaseClient를 생성하려면 newBuilder()를 사용합니다. 구매 관련 업데이트를 수신하려면 setListener()를 호출하여 PurchasesUpdatedListener에 대한 참조를 전달해야 합니다. 이 리스너는 앱의 모든 구매 관련 업데이트를 수신합니다.
setBase64PublicKey()는 SDK에서 구매 데이터 위변조에 대한 서명 확인 작업을 합니다. 옵션 값이지만 사용하는 것을 권장합니다.
라이선스 키는 원스토어 개발자 센터에서 확인 가능합니다.
라이선스 키의 경우, 앱 내 코드로 저장하기 보다는 보안이 될 수 있도록 서버 등을 이용하여 전달받아 사용하는 것을 권장합니다.
privateval listener =PurchasesUpdatedListener { iapResult, purchases ->// To be implemented in a later section.}privatevar purchaseClient = PurchaseClient.newBuilder(activity) .setListener(listener) .setBase64PublicKey(/*your public key*/) // optional .build()
privatePurchasesUpdatedListener listener =new PurchasesUpdatedListener { @OverridepublicvoidonPurchasesUpdated(IapResult iapResult,List<PurchaseData>) {// To be implemented in a later section. }};privatePurchaseClient purchaseClient =PurchaseClient.newBuilder(activity).setListener(listener).setBase64PublicKey(/*your public key*/) // optional.build();
원스토어 서비스 연결 설정
PurchaseClient를 생성 후 원스토어 서비스에 연결해야 합니다.
연결하려면 startConnection()을 호출합니다. 연결 프로세스는 비동기이며 클라이언트 연결이 완료되면 PurchaseClientStateListener를 통해 콜백이 수신됩니다.
다음 예는 연결을 시작하고 사용 준비가 되었는지 테스트하는 방법입니다.
purchaseClient.startConnection(object : PurchaseClientStateListener {overridefunonSetupFinished(iapResult: IapResult) {if (iapResult.isSuccess) {// The PurchaseClient is ready. You can query purchases here. } }overridefunonServiceDisconnected() {// Try to restart the connection on the next request to// PurchaseClient by calling the startConnection() method. }})
purchaseClient.startConnection(new PurchaseClientStateListener { @OverridepublicvoidonSetupFinished(IapResult iapResult) {if (iapResult.isSuccess()) {// The PurchaseClient is ready. You can query purchases here. } } @OverridepublicvoidonServiceDisconnected() {// Try to restart the connection on the next request to// PurchaseClient by calling the startConnection() method. }});
상품 상세정보 조회하기
상품 상세정보를 조회하려면 queryProductDetailsAsync()를 호출합니다. 사용자에게 제품을 표시하기 전에 중요한 단계입니다.
queryProductDetailsAsync()를 호출할 때 setProductType()과 함께 원스토어 개발자 센터에서 생성된 인앱 상품 ID 문자열 목록을 지정하는 ProductDetailParams의 인스턴스를 전달합니다.
ProductType은 아래와 같습니다.
위의 모든 유형의 데이터를 한 번에 조회하고 싶을 경우는 ProductType.ALL 설정하면 됩니다.
비동기 작업 결과를 처리하려면 ProductDetailsListener 인터페이스를 구현해야 합니다.
val params = ProductDetailsParams.newBuilder() .setProductIdList(productIdList) .setProductType(ProductType.INAPP) .build()purchaseClient.queryProductDetailsAsync(params) { iapResult, productDetails ->// Process the result.}
ProductDetailsParams params =ProductDetailsParams.newBuilder().setProductIdList(productIdList).setProductType(ProductType.INAPP).build();purchaseClient.queryProductDetailsAsync(params,newProductDetailsListener() { @OverridepublicvoidonProductDetailsResponse(IapResult iapResult,List<ProductDetail>) {// Process the result. }});
구매 요청하기
앱에서 구매 요청을 하기 위해서는 기본 스레드에서 launchPurchaseFlow() 함수를 호출합니다.
이 함수는 queryProductDetailsAsync() 함수를 호출해서 얻은 ProductDetail 객체의 값을 토대로 PurchaseFlowParams 객체를 생성합니다.
PurchaseFlowParams 객체를 생성하려면 PurchaseFlowParams.Builder 클래스를 사용합니다.
setDeveloperPayload()는 개발사에서 임의로 입력한 값으로 최대 200byte입니다. 이 값은 결제 후에 데이터의 정합성과 부가 데이터를 확인하기 위해 사용할 수 있습니다.
setProductName()은 상품 이름을 결제 시 변경하여 노출하고 싶을 때 사용됩니다.
setQuantity()는 관리형 인앱 상품에만 적용되며 한 상품을 여러 개 구매할 때 사용됩니다.
원스토어는 사용자에게 할인 쿠폰, 캐시백 등의 다양한 혜택 프로모션을 진행하고 있습니다.
개발사는 구매 요청 시에 gameUserId, promotionApplicable 파라미터를 이용하여 앱을 사용하는 유저의 프로모션 참여를 제한하거나 허용할 수 있습니다.
개발사는 앱의 고유한 유저 식별 번호 및 프로모션 참여 여부를 선택하여 전달하고, 원스토어는 이 값을 토대로 사용자의 프로모션 혜택을 적용하게 됩니다.
gameUserId, promotionApplicable 파라미터는 옵션 값으로 원스토어 사업 부서 담당자와 프로모션에 대해 사전협의가 된 상태에서만 사용하여야 하며, 일반적인 경우에는 값을 보내지 않습니다.
또한, 사전협의가 되어 값을 보낼 경우에도 개인 정보보호를 위해 gameUserId는 hash된 고유한 값으로 전송하여야 합니다.
업그레이드 또는 다운그레이드의 경우도 구매 요청하기 로직을 수행하기 때문에 응답은 PurchasesUpdatedListener에서 수신합니다. 또한 구매 내역 조회하기에서도 요청 시 응답을 받을 수 있습니다.
비례 배분 모드로 구매했을 때도 일반 구매와 동일하게 PurchaseClient.acknowledgeAsync()를 사용하여 구매 확인을 해야 합니다.
구매 처리하기
구매가 완료되면 앱에서 구매 확인 처리를 해야 합니다. 대부분의 경우 앱은 PurchasesUpdatedListener를 통해 구매 알림을 받습니다.
또는 구매 내역 조회하기에서 설명된 것처럼 앱이 PurchaseClient.queryPurchasesAsync() 함수를 호출하여 처리하는 경우가 있습니다.
다음 메서드 중 하나를 사용해 구매를 확인할 수 있습니다.
소모성 제품인 경우 PurchaseClient.consumeAsync()를 사용합니다.
소모성 제품이 아니라면 PurchaseClient.acknowledgeAsync()를 사용합니다.
관리형 상품 소비하기 (consume)
관리형 상품은 소비를 하기 전까지는 재구매 할 수 없습니다.
상품을 소비하기 위해서는 consumeAsync()를 호출합니다. 또한 소비 작업 결과를 전달받으려면 ConsumeListener 인터페이스를 구현해야 합니다.
관리형 상품을 소비하지 않으면 영구성 형태의 상품 타입처럼 활용할 수 있으며, 구매 후 즉시 소비하면 소비성 형태의 상품으로도 활용됩니다.
또한 특정 기간 이후에 소비하면 기간제 형태의 상품으로 활용할 수 있습니다.
3일 이내에 구매를 확인(acknowledge) 또는 소비(consume)를 하지 않으면 사용자에게 상품이 지급되지 않았다고 판단되어 자동으로 환불됩니다.
funhandlePurchase(purchase: PurchaseData) {// Purchase retrieved from PurchaseClient#queryPurchasesAsync// or your PurchasesUpdatedListener.val purchase: PurchaseData=...// Verify the purchase.// Ensure entitlement was not already granted for this purchaseToken.// Grant entitlement to the user.val consumeParams = ConsumeParams.newBuilder() .setPurchaseData(purchase) .build() purchaseClient.consumeAsync(consumeParams) { iapResult, purchaseData ->// Process the result. }}
privatevoidhandlePurchase(PurchaseData purchase) {// Purchase retrieved from PurchaseClient#queryPurchasesAsync or your PurchasesUpdatedListener.PurchaseData purchase =...// Verify the purchase.// Ensure entitlement was not already granted for this purchaseToken.// Grant entitlement to the user.ConsumeParams consumeParams =ConsumeParams.newBuilder().setPurchaseData(purchase).build();purchaseClient.consumeAsync(consumeParams,newConsumeListener() { @OverridepublicvoidonConsumeResponse(IapResult iapResult,PurchaseData purchaseData) {// Process the result. } });}
소비 요청이 때로 실패할 수 있으므로 보안 백엔드 서버를 확인하여 각 구매 토큰이 사용되지 않았는지 확인해야 합니다. 그래야 앱이 동일한 구매에 대해 여러 번 자격을 부여하지 않습니다. 또는 자격을 부여하기 전에 성공적인 소비 응답을 받을 때까지 기다릴 수 있습니다.
구매 확인하기 (acknowledge)
비 소비성 상품에 대해 구매 확인 처리를 하려면 PurchaseClient.acknowledgeAsync() 함수를 사용합니다. 관리형 상품, 월정액 상품, 구독형 상품 모두 사용할 수 있습니다.
PurchaseData.isAcknowledged() 함수를 사용하여 구매 확인 되었는지를 판단 할 수 있습니다. 또한 구매 확인에 대한 작업 결과를 전달받으려면 AcknowledgeListener 인터페이스를 구현해야 합니다.
// Purchase retrieved from PurchaseClient#queryPurchasesAsync or your PurchasesUpdatedListener.funhandlePurchase(PurchaseData purchase) {if (purchase.purchaseState == PurchaseState.PURCHASED) {if (!purchase.isAcknowledged) {val acknowledgeParams = AcknowledgeParams.newBuilder() .setPurchaseData(purchase) .build() purchaseClient.acknowledgeAsync(acknowledgeParams) { iapResult, purchaseData ->// PurchaseClient by calling the queryPurchasesAsync() method. } } }}
// Purchase retrieved from PurchaseClient#queryPurchasesAsync or your PurchasesUpdatedListener.privatevoidhandlePurchase(purchase: PurchaseData) {if (purchase.getPurchaseState() ==PurchaseState.PURCHASED) {if (!purchase.isAcknowledged()) {AcknowledgeParams acknowledgeParams =AcknowledgeParams.newBuilder().setPurchaseData(purchase).build();purchaseClient.acknowledgeAsync(acknowledgeParams,newAcknowledgeListener() { @OverridepublicvoidonAcknowledgeResponse(IapResult iapResult,PurchaseData purchaseData) {// PurchaseClient by calling the queryPurchasesAsync() method. } }); } }}
AcknowledgeListener.onAcknowledgeResponse() 함수로 전달된 PurchaseData는 요청할 당시의 데이터이기 때문에 acknowledgeState 값이 변경되지 않습니다. 구매 내역 조회하기를 통해 변경된 데이터로 교체해야 합니다.
구매 내역 조회하기
PurchasesUpdatedListener를 사용하여 구매를 처리하는 것만으로는 모든 구매가 처리 되었다는 것을 보장할 수 없습니다. 앱에서 구매 추적을 놓치거나 구매를 인식하지 못할 수 있는 몇 가지 시나리오는 다음과 같습니다.
아래와 같은 시나리오에서 앱은 구매 응답을 받지 못하거나 구매를 인식하지 못하는 경우가 발생할 수 있습니다.
네트워크 문제: 사용자가 성공적으로 구매를 하였고, 원스토어에서 확인도 받았지만 기기가 PurchasesUpdatedListener를 통해 구매 알림을 받기 전에 네트워크 연결이 끊어졌을 경우
여러 기기: 한 기기에서 항목을 구입한 후 다른 기기로 전환하는 경우
이러한 경우를 대비하여 앱의 onCreate()나 onResume()에서 PurchaseClient.queryPurchasesAsync()를 호출하여 구매가 성공적으로 처리 되었는지 확인해야 합니다.
콜백 처리는 PurchasesUpdatedListener 와 동일합니다.
val queryPurchasesListener =QueryPurchasesListener { iapResult, purchases ->if (iapResult.isSuccess && purchases !=null) {for (purchase in purchases) {handlePurchase(purchase) } } elseif (iapResult.responseCode == ResponseCode.NEED_UPDATE) {// PurchaseClient by calling the launchUpdateOrInstallFlow() method. } elseif (iapResult.responseCode == ReponseCode.NEED_LOGIN) {// PurchaseClient by calling the launchLoginFlow() method. } else {// Handle any other error codes. }}purchaseClient.queryPurchasesAsync(ProductType.INAPP, queryPurchasesListener)
QueryPurchasesListener queryPurchasesListener =newQueryPurchasesListener() { @OverridepublicvoidonPurchasesResponse(IapResult iapResult, list<PurchaseData> purchases) { if (iapResult.isSuccess() && purchases !=null) {for (purchase in purchases) {handlePurchase(purchase) } } elseif (iapResult.getResponseCode() ==ResponseCode.NEED_UPDATE) {// PurchaseClient by calling the launchUpdateOrInstallFlow() method. } elseif (iapResult.getResponseCode() ==ReponseCode.NEED_LOGIN) {// PurchaseClient by calling the launchLoginFlow() method. } else {// Handle any other error codes. } }};purchaseClient.queryPurchasesAsync(ProductType.INAPP, queryPurchasesListener);
월정액 상품 상태 변경하기 (Deprecated)
월정액 상품은 최초 구매 후 익월 동일일에 갱신이 이루어지는 상품입니다. 월정액 상품의 상태는 PurchaseData.getRecurringState()를 통해 확인할 수 있습니다.
월정액 상품의 상태를 변경하려면 PurchaseClient.manageRecurringProductAsync()를 사용합니다.
RecurringProductParams 객체에 구매 데이터와 변경하려는 PurchaseClient.RecurringAction 값을 입력합니다.
SDK V21 (API V7) 부터는 새로운 월정액 상품을 만들 수 없습니다. 결제 주기가 한 달인 구독형 상품을 이용하세요.
// Purchase retrieved from PurchaseClient#queryPurchasesAsync or your PurchasesUpdatedListener.funmanageRecurring(purchase: PurchaseData) {val recurringParams = RecurringProductParams.newBuilder() .setPurchaseData(purchase) .setRecurringAction(RecurringAction.CANCEL | RecurringAction.REACTIVATE) .build() purchaseClient.manageRecurringProductAsync(recurringParams) { iapResult, purchaseData, action ->// PurchaseClient by calling the queryPurchasesAsync() method. }}
// Purchase retrieved from PurchaseClient#queryPurchasesAsync or your PurchasesUpdatedListener.privatevoidmanageRecurring(PurchaseData purchase) {RecurringProductParams recurringParams =RecurringProductParams.newBuilder().setPurchaseData(purchase).setRecurringAction(RecurringAction.CANCEL|RecurringAction.REACTIVATE).build();purchaseClient.manageRecurringProductAsync(recurringParams,newRecurringProductListener() { @OverridepublicvoidonRecurringResponse(IapResult iapResult,PurchaseData purchaseData,String action) {// PurchaseClient by calling the queryPurchasesAsync() method. } });}
RecurringProductListener.onRecurringResponse() 함수로 전달된 PurchaseData는 요청할 당시의 데이터이기 때문에 recurringState 값이 변경되지 않습니다. 구매 내역 조회하기를 통해 변경된 데이터로 교체해야 합니다.
정기 결제 관리 화면을 열기
정기 결제 중인 상품을 관리하는 화면을 띄울 수 있습니다.
매개변수로 SubscriptionsParams에 PurchaseData를 포함해서 넣으면 구매 데이터를 확인하여 해당 정기 결제 상품의 관리 화면을 실행합니다.
그러나 SubscriptionParams을 null 넣을 경우 사용자의 정기 결제 리스트 화면을 실행합니다.
SDK v19 이상부터 Server API를 사용하기 위해서는 마켓 구분 코드가 필요합니다.
getStoreInfoAsync()를 통해서 마켓 구분 코드를 획득할 수 있습니다.
purchaseClient.getStoreInfoAsync { iapResult, storeCode ->// Save storecode and use it in Server to Server API.}
purchaseClient.getStoreInfoAsync(newStoreInfoListener() { @OverridepublicvoidonStoreInfoResponse(IapResult iapResult,String storeCode) {// Save storecode and use it in Server to Server API. }});
원스토어 서비스 설치하기
원스토어 서비스의 버전이 낮거나 없을 경우 인앱결제를 이용할 수 없습니다. PurchaseClient.startConnection()을 통해 연결할 때 IapResult.getResponseCode()에서 확인할 수 있습니다.
RESULT_NEED_UPDATE가 발생하면 launchUpdateOrInstallFlow() 메서드를 호출해야 합니다.
val activity: Activity=...purchaseClient.launchUpdateOrInstallFlow(activity) { iapResult ->if (iapResult.isSuccess) {// If the installation is completed successfully,// you should try to reconnect with the ONE store service. // PurchaseClient by calling the startConnection() method. }}