상세 컨텐츠

본문 제목

안드로이드 결제, Billing Library Version, 인 앱 결제(In-app Billing) 관련

스마트기기개발관련/안드로이드 개발

by AlrepondTech 2013. 4. 3. 14:42

본문

반응형

 

 

 

=======================

=======================

=======================

 

 

 

인앱결제만들기 결제라이브러리3(Billing Library Version 3)이상 간단구현링크:

- developer.android.com/google/play/billing/integrate?hl=ko //

- puch-android.tistory.com/5 //

- witcheryoon.tistory.com/168 //

- gamjatwigim.tistory.com/93 //

- wonsohana.wordpress.com/2020/11/02/android-inappbilling-example-%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-%EC%9D%B8%EC%95%B1%EA%B2%B0%EC%A0%9C-%EC%98%88%EC%A0%9C/ // 

- faith-developer.tistory.com/159 //

- bemind.tistory.com/24 //

//--------------------------------------------------------------------------------------------------------------------

기존 안드로이드 이클립스개발자는 결제 모듈 정책으로 안드로이드 스튜디오로 이전해야 결제 개발이 편하게 될 것 같습니다.

결제라이브러리(Billing Library Version)버전별 유효 라이프사이클 주기 입니다.

나중에는 라이프사이클이 더 업데이트가 되겠지요.

결제기능이 있는 개발자분들은 이 사이클 주기를 염두해 두셔야 할 것 같습니다.

 

 

 

=======================

=======================

=======================

 

 

 

 

인앱 API 설명

http://developer.android.com/intl/ko/google/play/billing/api.html

 

구글인앱 결제 샘플(가끔옛날구글 샘플로 참고하여 먹통이 될 수 잇으니 항상 최신샘플을 참고하세요)

https://github.com/googlesamples/android-play-billing

 

 

 

=======================

=======================

=======================

 

 

 

 

안드로이드 결제 관련

 

옛날방식:

1. 관리되는 제품 : 소진이 불가능한(Non-consumable)영원히 사용자에게 귀속되는 상품,

                         똑같은 상품을 두번 이상 구매할 수 없는것.

                         유저는 단 한번만 구매를 할 수 있습니다.

                         즉, 결재를 해야 다른 스테이지가 열리는 구조의 앱에서 사용하면 좋을 듯.

 

2. 관리되지 않는 제품 : 소진이 가능함(Consumable)상품.

                                똑같은 상품을 계속해서 반복 구매하는 것이 가능함.

                                중복 구매가 가능함. 포인트 종류의 아이템류에 사용함.

 

3. 구독 : 한달 또는 일년 단위로 자동 연장되는 결제 방식을 의미함.

            음원 서비스들에서 주로 볼 수 있는 상품의 모슴

 

요즘방식

 

1. 관리되는 제품 : 소진이 불가능한(Non-consumable)영원히 사용자에게 귀속되는 상품,

                         똑같은 상품을 두번 이상 구매할 수 없는것.

                         유저는 단 한번만 구매를 할 수 있습니다.

                         즉, 결재를 해야 다른 스테이지가 열리는 구조의 앱에서 사용하면 좋을 듯.

                         (관리되지 않는 제품 통합으로 사용된다, 결제할때 소비를 하였다는 API 를 통해 다시 재구매 가능하다.)

                         

 

2. 구독 : 한달 또는 일년 단위로 자동 연장되는 결제 방식을 의미함.

            음원 서비스들에서 주로 볼 수 있는 상품의 모슴

 

 

 

 

=======================

=======================

=======================

 

 

 

 

 

출처: http://bitfund.net/bbs/board.php?bo_table=unity3d&wr_id=194&sst=wr_hit&sod=asc&sop=and&page=1

 

Unity에서 개발중인 앱에서 인앱 결제를 구현하고 테스트 할 때 막힌 곳 메모

자산 저장소 "Android In App Billing Plugin '을 사용하고 있습니다. 

 

주로 Google Developer Console의 설정 주위에서 생긴 일입니다.■ 앱 내 아이템 탭에서 각 항목의 상태를 "유효"로하고 있지 않았던 

 

 항목별로 활성화해야합니다.■ 응용 프로그램을 게시하는 동안하지 않았다 

 

 베타 테스트 또는 알파 테스트에 APK를 올려 

 

 "공개 중"상태로하지 않으면 과금 테스트를 할 수 없습니다.■ 응용 프로그램을 업 할 때 50MB를 초과 버린 

 

 50MB 미만으로 떨어 뜨릴 필요가 있습니다. 방법에는 여러 가지가 있지만 

 

 자원 류를 모두 깎아 빌드하고 응용 프로그램으로는 움직이지 성과물 (apk)을 

 

 업해도 과금 테스트 할 수있었습니다. 

 

 일단 Bundle Identifer · 인증서를 갖춘 애플리케이션을 올려 버리면 

 

 GooglePlay에서 응용 프로그램을 다운로드하지 않아도 테스트는 가능합니다.■ 응용 프로그램이 전송되는 RSA 키가 틀렸다 

 

 이 막혔다. RSA 키가 다른 있어도 GooglePlay 결제 완료 화면까지 도달 해 버린다. 

 

 실제로 청구가 Success하고 있지 않기 때문에, 이후에 실패한 결제 큐가 쌓여 버려, 

 

 Plugin의 queryInventory 메소드에서 다음 오류로 굴러 버렸 제품 목록을 취할 수 없게되었다. 

 

 (response : -1003 : Purchase signature verification failed) 

 

 RSA 키를 제대로하면 해결했지만, 지금까지 실패한 계정에 관해서는 큐를 소화 할 방법을 찾지 못하고 공장 출하 상태로 할 수밖에없는 모양 ...■ GooglePlayDeveloperConsole 계정 정보에서 "테스트 권한이있는 Gmail 계정"대상의 Gmail 계정을 등록하지 않았다. 

 

 등록하지 않았다 위에 등록해도 몇 시간은 반영되지 않았다 ...■ (추가) BundleIdentifer가 다른 빌드이었다 ... 

 

 Android에서 BundleIdentifer 서명과 다를 결제하려고했을 때 GooglePlay가내는 대화에서는 

 

 "이 버전은 청구 할 수 없습니다"인 문구가 나온다. 

 

 틀림없이 버전 않을까 생각했지만,이 메시지는 서명에 결함이있는 경우 등에서도 나오는 모양이므로주의. 

 

 (빌드 작업이 여러 관계에서 Identifer도 여러 있었기 때문에 일어난)위의 부분에 순차적으로 막혀 결국 과금 테스트가 성공했습니다. 

 

그 밖에도 막힌 곳이 있었다 느끼기 때문에 기억하면 비망록으로 추가합니다.

그래도 Android 결제 테스트 유행 어려운 ... 

 

 

 

=======================

=======================

=======================

 

 

 

 

 

출처: http://westwoodforever.blogspot.kr/2012/11/google-play-in-app-billing_14.html

 

 안드로이드 인앱 결제 작업중에 나온 오류를 따로 정리해봅니다.
 

 

 결제 요청을 하는 순간 '항목을 찾을 수 없습니다.' 오류가 나오고 확인을 누르면 바로 제대로 된 인앱 제품 정보가 나오는 증상이 있습니다. 이 에러는 Google Play Billing Library에 있는 Dungeons Sample를 연동해서 나온 오류입니다.

 

 Dungeons.java의 소스중 onClick 함수에서 Buy버튼을 클릭했을 때 mBillingService.requestPurchase를 처리하는 if, else if 문 문제입니다. 딱 보시면 mBillingService.requestPurchase 리턴값에 따라 한번 더 mBillingService.requestPurchase 처리 될 수 있는 구조입니다.

///< 구독이 아니면 if( mManagedType != Managed.SUBSCRIPTION ) {   if( !mBillingService.requestPurchase(mSku, Consts.ITEM_TYPE_INAPP, mPayloadContents) )   {     showDialog(DIALOG_BILLING_NOT_SUPPORTED_ID);   } } ///< 구독 else {   if( !mBillingService.requestPurchase(mSku, Consts.ITEM_TYPE_SUBSCRIPTION, mPayloadContents) )   {     showDialog(DIALOG_SUBSCRIPTIONS_NOT_SUPPORTED_ID);   } } 


 전 이렇게 수정 후 해결했습니다.


 

 '이 버전의 애플리케이션에서는 Google Play를 통한 결제를 사용할 수 없습니다. 자세한 내용은 도움말 센터를 참조하세요.' 애플리케이션 오류가 발생할 수 있습니다. 확인을 누르면 아래와 같은 로그가 발생합니다.

MarketBillingService.sendResponseCode: Sending response RESULT_DEVELOPER_ERROR for request xxxxxxxxxxxxxx to org.xxxx.game.

 Google Play 개발자 센터에 등록한 APK와 기기에서 개발 테스트 중인 App의 버젼이 달라서 생기는 문제라고 합니다. 하긴 지금 개발자 센터에는 Billing 퍼미션만 추가된 서명된 APK가 올라가 있고, 현재 이클립스로 개발 테스트 중인 App은 Dungeons Sample을 붙여서 연동 개발 테스트까지 하고 있으니 더 개발된 상태죠.

 그래서 서명된 APK를 새로 만들어 개발자 센터에도 올리고 디바이스에 APK를 넣어서 ASTRO 파일 관리자로 폰에 설치를 합니다. 이클립스에서 실행하는 것은 안됩니다. 테스트는 약 1시간 정도 기다린 후 해보시기 바랍니다. 새로 올린 APK가 바로 갱신 안되는 듯 싶네요. 그리고 다시 실행 후 구입을 시도하면 잘 되거나 다른 에러가 발생합니다.


 

 '요청하신 항목은 구매할 수 없습니다.' 구입할 수 없음 에러가 발생할 수 있습니다. 확인을 클릭 누르면 아래와 같은 로그가 발생합니다.

MarketBillingService.sendResponseCode: Sending response RESULT_ITEM_UNAVAILABLE for request xxxxxxxxxxxxxxxx to org.xxxx.game.

 

 

 

 판매자 계정과 기기에 로그인 된 구글 계정이 같아서 생기는 것이라는 말도 있습니다만 저는 회사 개발자 계정으로 테스트 중이라 제 계정과 같을리는 없죠. 개발자 센터 -> 프로필 수정에 보면 테스트 계정이라는 항목이 있는데, 여기에 제 구글 계정을 넣고 저장을 한 후 해봤지만 역시나 안되는군요.

 

  제 경우에는 추가 된 인앱 제품을 게시를 안해서 생긴 문제였습니다. 테스트 중인 인앱 제품을 '게시완료' 후 테스트 하니 결제 진행이 되네요. 물론 App은 '게시 안됨'이어도 됩니다.

 사용자는 이 항목을 구입할 수 없습니다 에러는 따로 정리했습니다.

 

 

 

=======================

=======================

=======================

 

 

 

 

 

구글 인앱 빌링 버전3

 

1. Android SDK Manager 에서 Extra - Google Play Billing Libarary를 설치합니다.

2. %ANDROID_SDK%\\extras\google\play_billing 경로를 TrivialDrive 프로젝트를 import 합니다.

3. com.android.vending.billing 패키지를 복사합니다.

4. com.example.android.trivialdrivesample.util 패키지를 복사하고 패키징명을 변경합니다.

5. AndroidManifest.xml에 BILLING 권한을 추가합니다.

6. export로 keystore로 서명한 APK 파일 내보내 플레이스토어 개발자 콘솔에 등록합니다.

7. 앱 정보와 인앱 아이템을 등록합니다. 인앱 아이템마다 상태를 Inactive -> Active로 변경해야 구입이 가능합니다.

8. TrivialDrive 프로젝트의 MainActivity를 참고하여 빌링로직을 구현합니다.

9. 다시 APK 파일 업로드 및 테스트

 


 

구입이 안될 때 확인 사항

 

 

 

 

1. 인앱아이템을 게시하지 않았을 경우(Inactive -> Active로 변경) - 게시로 바꾸고 3시간정도 있어야 적용됩니다.

2. 판매자 계정과 단말기의 계정이 같을 경우. 계정이 다른 단말기로 하셔야됩니다. 같은 단말기에 계정이 여러개일경우에도 발생합니다.

1. 안드로이드 마켓에 앱등록 (메니페스트에 권한을 부여하셨는지 확인하세요. 앱을 등록한 후에 '게시 안함' 상태로 설정하세요.)

2. Merchant Account 계정 등록하고 인앱 제품 게시. (인앱 제품은 게시 상태로 설정하세요)

3. 빌링과 관련없는 구글 계정을 이용하여 테스트 계정을 생성합니다.

4. 테스트 디바이스를 공장 초기화로 하셔서 테스트 계정을 등록합니다.

5. 안드로이드 마켓을 최신 버젼으로 업데이트 합니다.

6. 인앱 제품에 프로덕트 아이디를 이용하여 실제 결재를 테스트 합니다.

 

 

 

"이 버전의 애플리케이션에서는 Google Play를 통한 결제를 사용할수 없습니다."

 

여기서 이 버전은 어플의 버전일까요? 아님 구글 버전일까?

구글에 올린 테스트 apk랑 동일해야 한다. 버전을 동일한 것을 이용해야 테스트 할 수 있다.

 

 

 

 

 

=======================

=======================

=======================

 

 

 

 

 

출처: http://ggam.net/bbs/board.php?bo_table=unity3d&wr_id=198

 

 

1. 안드로이드 인앱결제는 V3 로 처리하면 간단히 처리된다.

참고사이트 - http://developer.android.com/google/play/billing/billing_integrate.html

 

영어로 씨부려서 이해하기 빡시지만, 구글번역기 돌려가면서 다 읽어봐라. 피가 되고 살이된다.

 

2. 이클립스에서 SDK - EXTRAS - 구글 플레이 빌링 라이브러리.... 맞나? 암턴 다운받고... 해당폴더가 다운된 디렉터리가서 aidl 을 프로젝트에 추가한다.

src/'com.android.vending.billing' 으로 패키지 추가로 처리한 후 해당위치에 파일을 복사. 

잘되었다면, gen폴더쪽에  com/android/vending/bill~~/`~AppBill~~~~.java 파일이 생성된다.

 

3. aidl 파일말고 또 뒤에 보면 샘플 있는데, 그거 참고해서 구성하면 참 쉽게 구성된다.

 

 

혹시 유니티로 이거 하려거던, 안드로이드 빌드할때 apk 파일로 바로뽑지말고,

프로젝트로 뽑은뒤에

 

import -> General -> Existing Projcet~~~~ 를 클릭해서 임포트 시키고, 적용시키자.

 

에러가나면 빌드 버전을 1.6으로 하면된다.

 

 

혹시라도 샘플파일 프로젝트에 넣고 실행하는데, IabHelper.OnIabPurchaseFinishedListener 가 호출되지 않는다면,

 

protected void onActivityResult(int requestCode, int resultCode, Intent data) { 

이 함수가 있는지부터 확인해봐라.

 

그리고 샘플파일 그대로 쫓아하면 에러나는데,

 

flagEndAsync();

이거 관련해서 검색해서 처리해라.

 

이건 내가 까먹지 않으려고 적어둔건데....

 

걍 나처럼 개고생하는 사람들 많을거같아서 적어두는글이니깐,

 

열심히 구글링해서 데이터들을 찾아봐라. 

 

아... 참고로 인앱결제 처리할때 아이템 추가하는건 전부다 관리되는 아이템으로 처리하고,

 

여러번 구매할 수 있는 제품은 구매시 바로바로 소비시켜라. 안그러면 한번 구매하고 담에 구매못한다.

 

구독제품은 매달 과금되는 제품에 한해서 처리하니깐... 그런걸로 처리할 생각은 하지마라.

 

IabHelper의 launchPurchaseFlow 버그

 

 

            if (response != BILLING_RESPONSE_RESULT_OK)

            {

                logError("Unable to buy item, Error response: " + getResponseDesc(response));

 

                result = new IabResult(response, "Unable to buy item");

                if (listener != null) listener.onIabPurchaseFinished(result, null);

            }

 

실패 했을 때 리턴을 해줘야 하는데 안해주고 있음 아래와 같이 변경 해야됨

 

 

            if (response != BILLING_RESPONSE_RESULT_OK)

            {

                logError("Unable to buy item, Error response: " + getResponseDesc(response));

 

                result = new IabResult(response, "Unable to buy item");

                if (listener != null) listener.onIabPurchaseFinished(result, null);

                

                flagEndAsync();

                return;

            } 

 

 

 

 

 

=======================

=======================

=======================

 

 

 

 

출처: http://www.masterqna.com/android/17634/%EC%9D%B8%EC%95%B1%EB%B9%8C%EB%A7%81-%EC%A7%88%EB%AC%B8%EC%9E%85%EB%8B%88%EB%8B%A4-%EA%B4%80%EB%A6%AC%EB%90%98%EB%8A%94%EC%83%81%ED%92%88%EC%97%90-%EB%8C%80%ED%95%B4-%EA%B6%81%EA%B8%88%ED%95%A9%EB%8B%88%EB%8B%A4

 

현재 인앱빌링을 적용중에 있습니다.

그런데 마켓페이지에서 인앱상품을 추가하고 잇는데

관리되는 상품과 관리되지 않는 상품이 있는데요.

구글링해서 찾아보니까 인앱빌링 v3버전을 적용하려면 무조건 관리되는 상품으로 하라고 하더군요.

그런데 관리되는 상품은 한계정당 한번만 구매할수있는 상품이고,

관리되지 않는 상품은 한계정당 여러번 구매할수있는 상품으로 알고 있습니다.

그럼 예를들어 캐쉬골드나 이런걸 충전한다치면,

관리되는 상품으로 등록해놓으면 계정당 한번만 충전가능한거아닌가요?

v3에서는 무조건 관리되는 상품으로 해야된다는데 잘 이해가 안가네요;;

조언 부탁드립니다.

 

---------------------------------------------------------------------

 

구글링해서 찾아보니까 인앱빌링 v3버전을 적용하려면 무조건 관리되는 상품으로 하라고 하더군요.

이말이 틀렸음.. unmanagement type item 등록이 가능함

 

 

 

=======================

=======================

=======================

 

 

 

 

출처: http://www.masterqna.com/android/12345/%EC%9D%B8%EC%95%B1%EA%B2%B0%EC%A0%9C%EC%97%90-%EB%93%B1%EB%A1%9D%ED%95%98%EB%8A%94-%EC%95%84%EC%9D%B4%ED%85%9C%EC%97%90%EB%8C%80%ED%95%B4-%EC%A7%88%EB%AC%B8%EC%9A%94

 

책을 보는 어플을 만드는중인데요

 

책을 한권마다 사는건데

 

관리되지 않는제품 으로 해서 하나 등록했는데

 

어플에서 책이 2권있는데 1권 살때는 잘됬는데 2권 살때 누르니 이미 구매한 아이템이라고 나오는데

 

관리되는 제품 설명을 보니 계정당 한번밖에 못사는 어쩌구 써있더라고요.

 

그래서 관리되지 않은제품으로 등록한건데

 

설명이 두서 없어서 인앱결제 해보신분 있으시면 답변좀. 부탁드립니다.

 

계정은 테스트계정 등록해놓은걸로 결제 하고 있습니다.

 

 

-------------------------------------------------------------

 

관리되지 않는 제품이더라도 제품을 소모해야 동일한 ID의 제품을 구입할 수 있습니다.

구매 후 사용처리 하시면 계속해서 구매 가능 합니다.

 

아 그렇군요. 감사합니다.
소모는 comsumeasyn 에서 하는거 맞지요?
지금 iab v3 으로 하고 있습니다.

------------

네...

consumeAsync 사용하시면 됩니다.

 

 

 

=======================

=======================

=======================

 

 

 

 

출처 : http://crowjdh.blogspot.kr/2013/11/v3.html

 

Ch1. 준비

0. Android SDK Manager -> Extras -> Google Play Billing Library 다운받고 설치한다.

 

1. Google Play Developer Console 여기로 가서 내 앱 추가

 - Prepare Store Listing 상태로 추가.

 

2. <SDK Path>/extras/google/play_billing/IInAppBillingService.aidl 파일을 프로젝트의 com.android.vending.billing 에 복사한다.

 

3. <SDK Path>/extras/google/play_billing/samples/

TrivialDrive/src/com/example/android/trivialdrivesample/util 의 클래스들을 내 프로젝트의 적절한 곳으로 복사한다.

*패키지명은 내 프로젝트 패키지 하위 패키지로 설정한다.

 

4. 메니페스트 파일의 <manifest>태그 아래에

<uses-permission android:name="com.android.vending.BILLING" />

퍼미션을 추가한다.

5. 인앱빌링을 사용하고자 하는 액티비티의 onCreate내에서 IabHelper 인스턴스를 생성한다. 이때 넘겨주는 license key는 아래와 같은 방법으로 구성, 보안을 높이는 것을 추천한다.

*license key는

Google Play Developer Console -> 위 1에서 등록한 내 어플리케이션 -> Services & APIs

여기에서 확인한다.

Security Recommendation: It is highly recommended that you do not hard-code the exact public license key string value as provided by Google Play. Instead, you can construct the whole public license key string at runtime from substrings, or retrieve it from an encrypted store, before passing it to the constructor. This approach makes it more difficult for malicious third-parties to modify the public license key string in your APK file.

 

 

6. IabHelper인스턴스에 startSetup메소드에 콜백 등록하자. (http://developer.android.com/training/in-app-billing/preparing-iab-app.html#Connect 참조)

6.1 해당 액티비티에서 인앱결제가 완료됐을 경우

@Override
public void onDestroy() {
   super.onDestroy();
   if (mHelper != null) mHelper.dispose();
   mHelper = null;
}

이처럼 연결을 해제한다.

* 연결이 실패할 경우 IabHelper 클래스의 dispose() 메서드 내에서 에러가 나는 경우가 있다.

연결이 실패할 경우 startSetup 메서드에서 mServiceConn 객체 인스턴스를 만들 때 onServiceDisconnected 콜백이 불리면 mService 을 null로 만들어준다.

if (mContext != null) mContext.unbindService(mServiceConn);

위의 줄을

if (mContext != null && mService != null) mContext.unbindService(mServiceConn);

 

위처럼 바꾸자.

7.1 릴리즈용 키스토어를 사용해 사인된 apk파일을 생성한다.(디버그용 키스토어어는 안됨)

(http://www.androidpub.com/35445 참조)

* conversion to dalvik format failed with error 1 에러 발생시

project properties -> java build path -> Libraries

에서 의심가는 라이브러리를 제거하고 다시 추가해보자.

android-support-v4.jar을 업데이트한 후에 이 에러가 발생할 수 있다.

 

* 자신의 프로젝트나 라이브러리로 임포트한 프로젝트의 values/strings.xml 파일에서 Missing Translation 이라는 린트 에러가 날 때는

1. 아직 번역이 덜 끝났을 경우 :

<resources

   xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">

위 처럼 우선 무시하고 번역이 끝났을 경우 번역을 추가하고 위의 속성을 제거한다.

2. 번역이 필요없는 문자열의 경우 :

<string name="hello" translatable="false">hello</string>

위 처럼 translatable=”false”라는 속성을 주자.

 

7.2 Google Play Developer Console -> 위 1에서 등록한 내 어플리케이션 -> APK탭

에 들어가서 APK 업로드하기기(퍼블리시 하지 않는다)

7.3 Google Play Developer Console -> 위 1에서 등록한 내 어플리케이션 -> In-app Products

에 들어가서 새 제품을 추가하고 continue를 누른다.

*Type : Managed product를 선택한다.(일반적인 소모성 아이템의 경우)

*Product Id : application에서 이 아이템을 특정하는 데에 필요한 아이디를 입력.

(네이밍 규칙은 https://support.google.com/googleplay/android-developer/answer/1072599?hl=ko 여기를 참조)

7.4 추가 정보 입력 후 위의 save버튼 옆 inactive 버튼을 눌러 active로 바꿔준다.

 

*Warning: It may take up to 2-3 hours after uploading the APK for Google Play to recognize your updated APK version. If you try to test your application before your uploaded APK is recognized by Google Play, your application will receive a ‘purchase cancelled’ response with an error message “This version of the application is not enabled for In-app Billing.”

 

 

Ch.2 구현

인앱 구매에서 일반적으로 필요한 시나리오는 다음과 같다.

(아래는 모두 셋업이 끝난 후, 즉 IabHelper.startSetup(OnIabSetupFinishedListener listener)의 콜백에서 result.isSuccess()가 true를 반환한 경우에 해야 한다.)

 

1. 내가 등록한 아이템의 정보(이름, 가격 등의 세부 정보)를 알아오기.

2. 구매 절차

 

일반적으로 OnIabSetupFinishedListener에서 위 1을 수행해 아이템의 이름과 가격을 가져오고 난 뒤 그 정보를 유저에게 보여준다. 유저가 구매 버튼을 누른 경우 위 2를 수행한다.

 

1. 아이템 확인

1. Ch.1 - 6의 startSetup메소드의 결과 연결이 수립된 후 불리는 OnIabSetupFinishedListener에서 아래와 같이 인앱제품의 세부사항을 요청할 수 있다.

인앱 제품의 sku(고유 아이디. 위 Ch.1 - 7.3 참고)를 리스트에 넣고 인벤토리의 정보를 요청한다.

 

List<String> additionalSkuList = new ArrayList<String>();

additionalSkuList.add(IAB.SKU_BG_RED);

additionalSkuList.add(IAB.SKU_BG_YELLOW);

mHelper.queryInventoryAsync(true, additionalSkuList,mQueryFinishedListener);

 

2. 세부사항 요청의 결과를 받아올 콜백 메소드를 선언하자. 위에서 요청한 인벤토리의 정보가 아래의 콜백으로 들어온다.

 

private IabHelper.QueryInventoryFinishedListener

           mQueryFinishedListener = new IabHelper.QueryInventoryFinishedListener() {

           public void onQueryInventoryFinished(IabResult result, Inventory inventory)  

           {

              if (result.isFailure()) {

                 // handle error

                 return;

               }

 

               String redPrice =

                  inventory.getSkuDetails(IAB.SKU_BG_RED).getPrice();

               String yellowPrice =

                  inventory.getSkuDetails(IAB.SKU_BG_YELLOW).getPrice();

 

               // update the UI

               TextView resultView = (TextView)findViewById(R.id.result);

               resultView.setText("red price : " + redPrice + ", yellow price : " + yellowPrice);

           }

        };

 

2. 아이템 구매

1. 아이템 구매 요청을 날리기 전 액티비티의 onActivityResult에서 아래와 같은 처리를 해야 한다.

빨간 색으로 하이라이트한 부분이 중요하다. 이 부분을 처리해주지 않으면 정상적으로 구매절차가 이뤄지지 않는다.

 

 

 

@Override

    protected void onActivityResult(int requestCode, int resultCode, Intent data) {

        LogUtil.message(TAG, "requestCode : " + requestCode + ", resultCode : " + resultCode + ", data : " + data);

 

        if (!mHelper.handleActivityResult(requestCode, resultCode, data)) {

            // not handled, so handle it ourselves (here's where you'd

            // perform any handling of activity results not related to in-app

            // billing...

            super.onActivityResult(requestCode, resultCode, data);

        }

        else {

            Log.d(TAG, "onActivityResult handled by IABUtil.");

        }

    }

 
2. 위에서 인벤토리의 정보를 가져온 후 유저가 구매버튼을 누르면 아래의 메서드를 실행, 구매절차를 수행한다.
 
mHelper.launchPurchaseFlow(MarketAct.this, IAB.SKU_BG_RED, IabHelper.ITEM_TYPE_INAPP, RC_REQUEST, mPurchaseFinishedListener, "test payload");
 

인자값들

1) 엑티비티 인스턴스

2) 구매할 인앱 아이템 ID(SKU)

3) 아이템의 종류.

1. 일반 인앱 아이템 : IabHelper.ITEM_TYPE_INAPP

2. 정기구독 : IabHelper.ITEM_TYPE_SUBS

4) 임의 정수. 구매과정이 끝나면 onActivityResult의 requestCode로 반환된다. 자세한 내용은 후에 적는다.

5) 구매 종료 후 불려질 콜백. 여기서 상황에 따라 데이터와 UI를 업데이트 해준다.

6) ***

 

https://play.google.com/apps/publish/

1. All applications에 들어가서 테스트할 어플리케이션이 등록되어 있는지 확인.

2. Setting -> Account details

에서 Gmail accounts with testing access 에 자신이 사용하는 디바이스의 기본 계정을 추가한다.

* 기본 계정을 변경하려면 디바이스를 공장초기화하고 등록해야 한다(고 한다).

3. ch.1 - 7.1 ~ 7.2의 과정을 거친 apk파일을 올려야 테스트 구매를 할 수 있다. 릴리즈용으로 사인된 apk를 업로드하자.

(서버측의 인증은 대중 없다. 새로 apk를 올린 후 정상적으로 인앱 확인, 구매를 하려면 15분 정도 소요된다고는 하지만 테스트결과 주말의 경우 하루가 지나야 인증되는 경우도 있었다.)

4. 마찬가지로 이클립스에서 실행한 앱으로는 테스트 구매를 할 수 없다. 위 3번 단계에서 사인한 apk를 디바이스에 직접 설치하자.

 

*명령어(Mac OS 기준) :

언인스톨 : <sdk path>/platform-tools/adb [-s 디바이스 번호] uninstall 패키지명

인스톨 : <sdk path>/platform-tools/adb [-s 디바이스 번호] install 파일이름.apk

 

*디바이스 번호는

<sdk path>/platform-tools/adb devices

로 확인할 수 있다. 여러 개의 디바이스가 연결된 상황이 아니라면 이 옵션은 무시해도 좋다.

 

 

3. 아이템 소모

소모성 아이템의 경우 아이템을 사용하거나 앱이 구동될 때 체크하는 것이 좋다.

앱이 구동될 때 체크하는 이유는 소모성 아이템은 구입 즉시 유저에게 제공해야 하기 때문이다. 만약 소모성 아이템이 구글 플레이 서버에 남아있다면 유저에게 제공되지 않았다는 것을 뜻한다.

또한 소모성 아이템은 구글 플레이 서버에 소모 요청을 하지 않으면 다시 구매할 수 없기도 하므로 꼭 소모해주자.

 

mHelper.consumeAsync(purchase, mConsumeFinishedListener);

 

1) 소모할 아이템의 Purchase객체

2) 콜백. 변경된 데이터 반영과 UI 업데이트.

 

 

부록 2. 참고 문서

API Document

http://developer.android.com/google/play/billing/index.html

 

Lesson

http://developer.android.com/training/in-app-billing/index.html

 

Managing application

https://play.google.com/apps/publish/

 

Handling In-app-purchase orders

https://wallet.google.com/merchant/

 

 

 

 

반응형

 

728x90

 

 

 

=======================

=======================

=======================

 

 

 

 

출처: http://it77.tistory.com/74

 

안녕하세요.. 답답합니다 네 답답합니다. v2에서 v3으로 바뀐지 오래된것같은데 왜 저는 이제야 v3을 적용한것일까요.. 인터넷에 정보도 잘 없는것같고.. ( 물론 검색하니 나오긴합니다. 제가 할줄몰라 그런건지.. 허허 ) 삽질하는분 없으셨으면 하는 바람에 정리한번 해봅니다.

( 사실 다음에 또 인앱 구현할 일 있을때 이거보고 기억 더듬으며 편히 하게위함.... 은 비밀 )

구글 API문서도 도움되었지만, 한국어로 설명되어있으면 좋을것 같은 생각에.. 한 자 적어봅니다.

 

우선 SDK매니저 들어가셔서 

 

 

 

 

사진에 보이는 Google Play Billing Libary 를 설치합니다.

 

그럼 SDK - extra -google - play_billing 폴더가 생길겁니다.

안에 샘플코드도 들어있으니 참고하시면 됩니다. ( 사실 전 샘플코드 별로 도움 안되었어요.. ㅜㅜ )

 

Sample폴더 안에 TrivialDrive 폴더가 있으면 인앱 버전3 이 맞습니다.

 

src폴더에 있는 java파일을 본인 프로젝트로 복사해줍니다.

android폴더에있는 android.vending.billing 파일도 역시 복사하셔야 되며 이 파일은 패키지명을 수정하면 안됩니다.


Manifest에 다음을 추가해주세요.

<uses-permission android:name="com.android.vending.BILLING"/>

 

이제 기본세팅은 끝났습니다. 인앱 Activity를 코딩해볼까요

 

전역변수로

IInAppBillingService mService; IabHelper mHelper;

 

 

 

선언해주세요.

 

그리고

ServiceConnection mServiceConn = new ServiceConnection() { 		@Override 		public void onServiceDisconnected(ComponentName name) { 			mService = null; 		}  		@Override 		public void onServiceConnected(ComponentName name, IBinder service) { 			mService = IInAppBillingService.Stub.asInterface(service); 		} 	}; 

추가,

onDestroy 에는

@Override 	public void onDestroy() { 		super.onDestroy(); 		if (mServiceConn != null) { 			unbindService(mServiceConn); 		} 	} 

 

와 같이 해줍니다.

 

onCreate안에다가

bindService(new Intent("com.android.vending.billing.InAppBillingService.BIND"), mServiceConn, Context.BIND_AUTO_CREATE);  String base64EncodedPublicKey = "";  (구글에서 발급받은 바이너리키를 입력해줍니다)  mHelper = new IabHelper(this, base64EncodedPublicKey); 		mHelper.enableDebugLogging(true); 		mHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() { 			public void onIabSetupFinished(IabResult result) { 				if (!result.isSuccess()) { // 구매오류처리 ( 토스트하나 띄우고 결제팝업 종료시키면 되겠습니다 ) 				}  					AlreadyPurchaseItems();    // AlreadyPurchaseItems(); 메서드는 구매목록을 초기화하는 메서드입니다. v3으로 넘어오면서 구매기록이 모두 남게 되는데 재구매 가능한 상품( 게임에서는 코인같은아이템은 ) 구매후 삭제해주어야 합니다.  이 메서드는 상품 구매전 혹은 후에 반드시 호출해야합니다. ( 재구매가 불가능한 1회성 아이템의경우 호출하면 안됩니다 )  			} 		}); 	} 

 

 

그리고 AlreadyPurchaseItems 메서드입니다.

public void AlreadyPurchaseItems() { 		try { 			Bundle ownedItems = mService.getPurchases(3, getPackageName(), "inapp", null); 			int response = ownedItems.getInt("RESPONSE_CODE"); 			if (response == 0) { 				ArrayList purchaseDataList = ownedItems 						.getStringArrayList("INAPP_PURCHASE_DATA_LIST"); 				String[] tokens = new String[purchaseDataList.size()]; 				for (int i = 0; i < purchaseDataList.size(); ++i) { 					String purchaseData = (String) purchaseDataList.get(i); 					JSONObject jo = new JSONObject(purchaseData); 					tokens[i] = jo.getString("purchaseToken"); 					// 여기서 tokens를 모두 컨슘 해주기 					mService.consumePurchase(3, getPackageName(), tokens[i]); 				} 			}  			// 토큰을 모두 컨슘했으니 구매 메서드 처리 		} catch (Exception e) { 			e.printStackTrace(); 		} 	} 

 

// 구매메서드 입니다.

public void Buy(String id_item) { 		// Var.ind_item = index; 		try { 			Bundle buyIntentBundle = mService.getBuyIntent(3, getPackageName(),	id_item, "inapp", "test"); 			PendingIntent pendingIntent = buyIntentBundle.getParcelable("BUY_INTENT");  			if (pendingIntent != null) { //				startIntentSenderForResult(pendingIntent.getIntentSender(), 1001, new Intent(), Integer.valueOf(0), Integer.valueOf(0), Integer.valueOf(0)); 				mHelper.launchPurchaseFlow(this, getPackageName(), 1001,  mPurchaseFinishedListener, "test"); // 위에 두줄 결제호출이 2가지가 있는데 위에것을 사용하면 결과가 onActivityResult 메서드로 가고, 밑에것을 사용하면 OnIabPurchaseFinishedListener 메서드로 갑니다.  (참고하세요!) 			} else { 				// 결제가 막혔다면 			} 		} catch (Exception e) { 			e.printStackTrace(); 		} 	} 

 

결과처리 메서드 2가지 다 설명드리겠습니다

우선 1번 방법

@Override 	protected void onActivityResult(int requestCode, int resultCode, Intent data) { 		System.out.println("requestCode : " + requestCode); 		System.out.println("resultCode : " + resultCode); 		if(requestCode == 1001) 			if (resultCode == RESULT_OK) { 				if (!mHelper.handleActivityResult(requestCode, resultCode, data)) { 					super.onActivityResult(requestCode, resultCode, data); 					 					int responseCode = data.getIntExtra("RESPONSE_CODE", 0); 					String purchaseData = data.getStringExtra("INAPP_PURCHASE_DATA"); 					String dataSignature = data.getStringExtra("INAPP_DATA_SIGNATURE");  // 여기서 아이템 추가 해주시면 됩니다. // 만약 서버로 영수증 체크후에 아이템 추가한다면, 서버로 purchaseData , dataSignature 2개 보내시면 됩니다.  					 				} else { // 구매취소 처리 				} 			}else{ // 구매취소 처리 			} 		else{ // 구매취소 처리 		} 	} 

 

2번째 방법입니다.

IabHelper.OnIabPurchaseFinishedListener mPurchaseFinishedListener  = new IabHelper.OnIabPurchaseFinishedListener() { 	   public void onIabPurchaseFinished(IabResult result, Purchase purchase)  	   { // 여기서 아이템 추가 해주시면 됩니다. // 만약 서버로 영수증 체크후에 아이템 추가한다면, 서버로 purchase.getOriginalJson() , purchase.getSignature() 2개 보내시면 됩니다. 	   } 	}; 

 

 

이렇게하면 인앱처리 끝납니다. 다른문의사항있으면 댓글남겨주시면 답변드릴수있도록할게요 ㅎㅎ

 

 

요청하시는분이 많아서 인앱부분 소스 올려드립니다. 참고하세요 ㅎ

 

 

 

=======================

=======================

=======================

 

 

 

 

출처: http://theeye.pe.kr/archives/2130

 

현재 글에서 설명되는 상품의 타입중에 관리되지 않는 제품 (Consumable/Unmanaged Product)는 언제부터였는지는 정확하지 않지만 현재 제거된 상태입니다. 이제는 모든 앱내 상품들은 Google Play에 의해 관리되게 됩니다. 기본적으로 모든 관리되는 제품은 구매가 성공적으로 이루어졌을 때 보유(Owned) 상태가 됩니다. 이 상태에서는 Google Play를 통해 같은 상품을 중복 구매할 수 없게 됩니다.

 

 

이 보유상태의 제품을 consumePurchase() 를 호출하여 소진(Consume)하여 다시한번 비보유(Unowned) 상태로 되돌릴 수 있습니다. 이 소진행위는 Google Play로 하여금 다시 해당 상품을 구매 가능한 상태로 되돌리며 이전의 구매 정보를 파기하게 됩니다.


현재 출시되고 있는 수많은 Android 어플리케이션이 앱내 구매(In-App Billing) 기능을 제공하고 있습니다. 이러한 구매 기능을 쉽게 접할 수 있는 어플리케이션중에 게임이 있는데요. 많은 게임들이 해킹의 피해를 입고 있고 특히 프리덤(Freedom)과 같은 결제 해킹 앱들에 의해 피해를 보는 경우가 제 생각보다 많다는것을 알았습니다. 이러한 결제 해킹 앱들의 경우 폰의 루팅이 필요한데요. 루팅폰을 사용중인 유저의 비중이 생각보다 많은것 같습니다. 이 문서는 이러한 해킹으로 부터 나의 수익을 지키는 방법에 대해 정리해 보았습니다. 먼저 Android에 대해 기술하고 다음은 iOS에 대해 또 글을 올리겠습니다. Google Play가 제공하는 In-App Billing 버전3를 기준으로 정리하였으며 사전 지식이 부족하실 경우 이전에 작성한 [Android In-App Billing 구현하기 (IAB Version 3)]를 먼저 읽어보시길 권장합니다.

In-App Billing 상품의 타입에 대해 알아보기

 

 

Android Developer Console에서 “인앱 상품” 메뉴에 들어가서 상품을 추가하게 되면 볼 수 있는 화면입니다. 여기서 3가지 타입을 제공하는것을 볼 수 있습니다. 하지만 IAB 버전3 API의 경우 관리되는 제품/구독 2가지의 타입을 제공한다고 생각하시면 됩니다. 관리되지 않는 제품을 선택할 때 다음과 같은 화면을 볼 수 있습니다.

 

 

뒤에서 좀 더 자세하게 설명을 드리겠지만 관리되는 제품과 관리되지 않는 제품은 IAB v3에서는 동일하게 관리되는 제품으로 취급됩니다. 하지만 관리되는 제품은 소진이 불가능한(Non-Consumable) 영원히 사용자에게 귀속되는 상품이며 관리되지 않는 제품은 소진이 가능한(Consumable) 상품으로 의미가 갈립니다. 소진이 불가능한 상품이라는 의미는 똑같은 상품을 두번 이상 구매할 수 없는것을 의미합니다. 소진이 가능한 상품의 경우 똑같은 상품을 계속해서 반복 구매하는 것이 가능합니다. 게임의 중간화폐(Currency)가 이경우에 해당될것 같습니다. 구독의 경우에는 다음과 같이 한달 또는 일년 단위로 자동 연장되는 결제 방식을 의미합니다. 음원 서비스들에서 주로 볼 수 있는 상품의 모습이라고 생각됩니다. 구독의 취소는 [Google 월렛의 내 구독 정보]에서 취소할 수 있습니다.

 

 

상품을 추가하실때 위와 같은 화면을 볼 수 있습니다. 정리해 보자면 Android 에서 제공하는 상품의 종류는 크게 “관리되는 제품”과 “구독” 2가지를 제공하며 “관리되는 제품”에는 반복 구매할 수 없는 귀속되는 소진 불가 상품과 반복구매가 가능한 소진 가능 상품이 존재합니다.

여기서 굉장히 중요한것 한가지는 “관리되는 제품 – 소진불가” 상품은 구글측에서 구매 내역을 신뢰할 수 있는 수준에서 관리해 준다는것입니다. “구독”역시 마찬가지입니다. 하지만 “관리되는 제품 – 소진 가능”한 상품은 개발사에서 구매 내역을 직접 관리해야 합니다.

유저로부터 발생하는 CS중 “결제를 분명히 했는데 아이템이 들어오지 않았다”는 대부분 이 “관리되는 제품 – 소진가능”한 상품들의 구매 과정에서 발생합니다.

In-App Billing 버전3 결제 과정

 

 

먼저 중요하게 봐야 하는 Google In-App Billing 플로우입니다. isBillingSupported() 메소드 호출을 통해 앱내 결제가 가능한지 확인합니다. 디바이스와 OS버전의 상황을 체크하게 되는데 한국의 경우 여기서 실패하는 경우는 없다고 보시면 될것 같습니다.

다음은 getPurchases() 메소드입니다. 이 메소드는 현재 유저가 (이미 구매하여) 소유하고 있는 상품들의 정보를 반환합니다. 결제 플로우인데 왜 갑자기 쌩뚱맞게 이것을 호출하는가 의구심이 들 수 있습니다. 여기서 이 메소드를 호출함으로써 다음과 같은 정보를 얻을 수 있습니다.

  • 유저가 기존에 구매한 “관리되는 상품 – 소진 불가” 상품의 리스트
  • 유저가 기존에 구매한 “관리되는 상품 – 소진 가능” 상품중 아직 소진되지 않은 상품의 리스트
  • 유저의 구독 상품의 리스트

결론부터 말씀드리자면 이 메소드는 앱의 구동시점에 호출해줄 필요가 있습니다. 앱의 구동 시점 또는 로그인 기반의 경우 로그인이 성공하는 시점(카카오 로그인 성공 등)에 이 메소드를 호출함으로써 유저가 기존에 구매한 상품들의 정보를 불러와 앱에 세팅할 수 있습니다.

그렇다면 “우리 회사는 유저가 구매한 아이템의 모든 정보를 우리 서버에 직접 저장하고 관리하고 있다. 그럼 이것을 호출할 필요가 없는가?” 라고 반문하실 수 있습니다. 제가 알기로는 대부분의 한국의 게임사들은 직접 서버를 보유하고 있고 구매한 상품의 정보를 서버에 직접 보관하시는것으로 알고 있습니다. 그렇다면 이 호출을 건너뛰셔도 상관없습니다. 하지만 그럼에도 불구하고 호출하셔야 하는 이유는 뒤에 좀 더 자세히 설명하겠지만 “관리되는 상품 – 소진 가능”한 상품의 경우 구매 직후 바로 소진을 하게 되는데요(게임내에 통용되는 화폐로 교환), 구매는 성공했는데 유저에게 상품을 지급하기 전에 오류로 인해 앱이 죽는다거나 하는 문제로 소진을 못하는 경우가 발생할 수 있습니다.

이러한 상품들의 경우에도 getPurchases()를 통해 정보를 받아오실 수 있습니다. 결제는 성공했지만 지급에는 실패한 상품의 경우 바로 지급을 처리해주시면 됩니다. 가령 상품 구매가 성공할 때 “500골드의 구매가 정상적으로 처리되었습니다” 라는 팝업을 띄우게 되어있다고 가정해 봅시다. 유저가 결제를 정상적으로 성공한 시점에 앱이 어떤 문제로 죽었습니다. 유저는 깜짝 놀라 앱을 다시 구동할 것입니다. 그리고 앱이 켜지자 마자 진행중이던 결제처리를 마저 진행하고 해당 팝업을 띄우시면 됩니다.

기존의 IAB v2의 결제직후 아이템 지급까지 안정성이 보장되지 않는 상황을 대처하기 위해 v3에서는 결제후 아이템의 지급시점까지 결제 내역을 관리해주도록 변경되었습니다. (심지어 “관리되지 않는 상품”조차도 소진시점까지 관리되는 상품으로써 관리를 해줍니다. 이말은 소진하기 전까지는 관리되는 상품과 동일하게 중복 구매가 불가능하다는것을 의미합니다.)

즉, “관리되는 상품 – 소진 불가”과 “구독”의 경우 결제가 성공한 시점부터 언제든지 getPurchases()를 호출하여 구매내역을 꺼내볼 수 있습니다. 하지만 “관리되는 상품 – 소진가능” 상품의 경우 결제 → 소진(지급)을 거쳐서 처리하도록 되어있습니다. 이 소진을 하기 전까지는 “관리되는 상품 – 소진가능”(관리되지 않는 제품)일지라도 Google이 관리해줍니다. 소진에 대해서는 뒤에서 또 이야기 하기로 하고 계속해서 플로우를 설명해 보겠습니다.

getSkuDetails()는 판매 가능한 상품들의 상세 정보를 리스트로 반환합니다. 여기서 중요한점은 기존에 Google Play에 정의해둔 상품의 ID들을 모두 알고 있어야 하며 이 ID들을 이용하여 메소드를 호출하게 됩니다. 이 메소드를 호출하여 얻을 수 있는 정보는 상품의 가격, 이름, 설명, 구매 타입이 있습니다.

유저가 보유하고 있는 상품의 경우 getBuyIntent()를 사용하여 구매를 진행할 수 있습니다. 이 메소드를 호출하기 위해서는 기존에 Google Developer Console에 정의해두었던 상품의 ID와 다른 추가적인 파라미터가 사용됩니다. 이후의 결제 진행은 다음과 같은 순서로 이루어집니다.

  1. getBuyIntent()를 호출하면 Google Play는 구글 체크아웃 결제창을 시작할 수 있는 PendingIntent를 포함한 Bundle을 반환합니다.
  2. 당신의 어플리케이션에서 startIntentSenderForResult를 이용하여 위의 PendingIntent를 실행합니다.
  3. 체크아웃 결제가 종료된다면 (성공적으로 결제가 되었던지 유저가 결제를 중도에 취소하였던지) Google Play는 결과를 담은 Intent를 onActivityResult 메소드로 보내줍니다. 결과 코드를 통해 구매가 성공적으로 진행되었는지 취소되었는지 여부를 확인할 수 있습니다. 응답 Intent에는 구매 트랜젝션을 식별하는데 사용가능한 유니크한 purchaseToken을 포함한 구매한 상품의 정보를 담고 있습니다.

 

 

이번에는 소진에 대해 알아보겠습니다. Google Play를 통해 판매할 수 있는 앱내 상품중에 유일하게 “관리되는 상품 – 소진가능 (관리되지 않는 상품)”만이 이 소진 메소드인 consumePurchase()를 사용합니다.

좀 더 정리하여 보면 IAB v3에서는 모든 앱내 상품이 관리됩니다. Google Developer Console에는 “관리되지 않는 상품”이라고 표시되지만 실제로는 관리됩니다. 다만 이 “관리되지 않는 상품”은 소진을 하기 전까지만 관리됩니다. 즉, 상품을 구매하여 소진하기 전까지 Google은 이 상품을 유저가 소유하였다고 직접 관리하게 됩니다. 이미 소유한 상품은 두번 구매할 수 없습니다. 그리고 명시적으로 소진을 하게 되면 이후에 다시 이 상품은 소유하지 않은 상태가 되고 재구매가 가능하게 됩니다.

여기서 말하는 소진은 게임에서는 게임내 화폐로의 교환을 의미합니다. 좀 더 쉽게 설명해 보자면 게임내에서 500골드를 1,000원에 판매하고 있었다면 결제를 통해서는 500골드 교환권을 구매한것이 됩니다. 이 500골드 교환권은 실제 500골드로 교환할때까지 Google이 관리해줍니다. 그리고 모든 상품들에 대해 똑같은 교환권을 2개 이상 가지는것은 불가능합니다. 500골드 교환권을 500골드로 교환해야 다시 500골드 교환권을 구매할 수 있습니다.

정리

너무 길게 설명한것 같은데 정리해 보면 각각의 상품들은 다음과 같은 과정을 거처 구매가 진행됩니다.

상품 타입 V3에서의 의미 결제 플로우
 관리되는 제품  관리되는 제품 – 소진 불가 (Non-Consumable)  getSkuDetails() → getBuyIntent() → startIntentSenderForResult() → 상품 지급
 관리되지 않는 제품  관리되는 제품  – 소진 가능 (Consumable)  getSkuDetails() → getBuyIntent() → startIntentSenderForResult() → consumePurchase() → 상품 지급
 구독  관리되는 제품 – 특정 기간동안 보유 (자동 결제)  getSkuDetails() → getBuyIntent() → startIntentSenderForResult() → 구독 시작

국내의 대부분의 게임이 결제를 통해 게임내에서 사용가능한 중간 화폐를 구매하는 모습을 차용하고 있다는것을 볼때 위의 3가지중에서 가장 중요한부분은 “관리되지 않는 제품”입니다. 소진을 하는 시점부터 Google이 관리를 해주지 않기 때문에 보안에 가장 취약한 상품 타입이기도 합니다.

결제 보안 시작하기

안드로이드 결제 보안을 설명하는데에는 구글이 IAB v3 예제로 제공하는 TriviaDrive 프로젝트를 이용하는것이 가장 좋다고 생각합니다. 해당 샘플 프로젝트를 Gradle 프로젝트로 컨버팅 하여 [이곳]에 올려두었으니 참고하시기 바랍니다. 프로젝트의 설명은 MainActivity의 코드를 가지고 해보겠습니다.

지금부터는 상품의 타입을 Google Developer Console에서 볼 수 있는 “관리되는 제품”, “관리되지 않는 제품”, “구독” 3가지로 명명하도록 하겠습니다. “관리되지 않는 제품”일지라도 소진 전까지는 관리가 된다는것을 유념해 주시기 바랍니다.

IAB v3를 이용하도록 만들어진 TriviaDrive 샘플 프로젝트는 3가지 타입의 상품을 구매하는 모든 로직이 포함되어있는 프로젝트입니다. 부가적인 기능들이 모두 잘 구현되어있는 예제이므로 개발중인 프로젝트에서 결제를 구현하실때는 이 프로젝트의 aidl 파일뿐만 아니라 util 디렉토리 이하의 모든 Java소스도 복사해서 사용하시기 바랍니다.

이 프로젝트는 단순한 운전 게임을 모티브로 만들어진 예제이며 자동차는 가스로 움직이며 가스를 저장하는 저장 탱크가 있습니다. 플레이어가 가스를 구매할때마다 탱크의 가스가 1/4씩 충전이 됩니다. 플레이어가 운전을 시작하면 이 가스가 점차 감소됩니다. (물론 이건 게임이니깐 한번에 1/4씩 감소됩니다)

플레이어는 “프리미엄 업그레이드”를 할 수 있습니다. 이 업그레이드를 하게 되면 유저는 기본적으로 부여되는 파란차가 아닌 빨간 차를 부여받게 됩니다.

마지막으로 플레이어는 “무한 가스” 상품을 구독할 수 있습니다. 이 상품을 구독하게 되면 구독하는 동안은 가스를 사용하지 않고 달릴 수 있게 됩니다.

각 상품의 소진 메커니즘에 대해 정리해보자면 다음과 같습니다.

프리미엄 업그레이드 : 이 상품은 소진을 하지 않습니다. 구매를 하는 시점부터 유저에게 귀속되며 이후 유저는 영원히 파란차 대신에 빨간차를 소유할 수 있게 됩니다.

무한 가스 : 이 상품은 “구독” 상품입니다. 마찬가지로 “구독” 상품은 소진되지 않습니다.

가스 : 가스를 구매하는 순간 상품은 유저에게 귀속됩니다. 그리고 이것을 소진함으로써 당신의 어플리케이션에 적용할 수 있습니다. 이 예제에서는 가스 탱크를 1/4 채우는 것을 의미합니다. 실제로는 구매 직후에 바로 가스탱크가 채워질것입니다. 이 시점에서 가스 상품은 소진되고 그로인한 영향이 당신의 게임에 영향을 끼치게 됩니다. 예를 들면 다음과 같은 시나리오로 동작합니다.

상태 설명
 구매 전  가스 탱크에 가스가 1/2 차 있음
 구매 진행중  가스 탱크에 가스가 1/2 차 있고 “가스” 상품을 보유함
 구매 직후  “가스” 상품을 소진함, 가스 탱크에 가스가 3/4가 됨
 구매 완료 후  가스 탱크에 가스가 3/4 차 있고 “가스” 상품을 더이상 보유하고 있지 않음

여기서 알아두어야 하는 다른 중요한 점은 유저가 “가스”상품을 구매하였지만 그것을 소진하기 전에 어플리케이션이 크래시 나거나 또는 다른 어떤 일이 발생할 경우입니다. 그래서 우리는 게임이 시작될 때 유저가 “가스”아이템을 보유하고 있는지를 확인할 것입니다. 만약 보유중이라면 그것을 바로 소진하고 게임상의 가스 탱크를 채울것입니다. 이것은 매우 중요한 부분입니다.

 

 

테스트를 위해 위와 같이 상품을 등록하였습니다. 실제로 소스상에서는 다음과 같이 상품의 ID를 미리 정의해 두었습니다.

 

 

 

이번에는 상품의 구매 처리를 담당하는 IabHelper를 초기화 하는 부분을 보겠습니다.

 

 

여기서 유심히 봐야 하는 부분은 base64EncodedPublicKey라는 변수입니다. 이 값은 앱마다 다른 공개키 이며 Google Developer Console에서 확인이 가능합니다.

 

 

위의 값을 넣어주시면 됩니다. 이 값을 이용하여 구매가 성공하였을 때 Google Play로 부터 넘겨받은 데이터가 변조되었는지 여부를 검증할 수 있습니다.

보안팁 : 이 키는 공개키로써 이 키 자체가 어떤 비밀 정보를 담고 있지는 않습니다. 하지만 공격자가 이 키값을 손쉽게 위변조 하는것을 원치 않으므로 약간의 불폄함을 제공하는것도 방법입니다. 위의 코드를 게임내의 코드상에 직접 포함하는것보다는 조금 꼬아놓은 데이터를 포함시킨 뒤에 게임이 구동하는 시점에 조작을 통해 풀어서 사용하는것을 권장합니다. (가령 XOR 연산) 자체 서버가 있다면 XOR 연산에 사용할 키를 서버로부터 전송받는것도 방법일 것입니다. 어떤 방법을 사용해서 이부분은 완벽한 보안을 이루긴 어렵겠지만 공격자를 귀찮게 한다는 것에 의의가 있습니다.

 

 

 

 

 

 

 

 

 

이제 mHelper를 초기화합니다. 이 메소드를 통해 IAB 서비스 커넥션을 생성하고 IAB 구현이 가능한지 여부를 확인합니다. 여기서 중요한 부분은 queryInventoryAsync() 메소드를 호출한다는 것입니다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

앱이 구동되지마자 IabHelper를 초기화 하고 가장먼저 호출한 코드입니다. 구매 내역을 가져와서 “프리미엄 업그레이드” 여부를 세팅하고 “무한 가스” 플랜을 구독중인지 여부를 게임내에 세팅하였습니다. 마지막으로 아직 소진되지 않은 “가스”가 존재하는지를 확인하여 소진시켜주는 과정입니다.

제작중인 게임 역시 유저의 결제는 정상적으로 이루어졌지만 어떤 오류로 인해 실제 지급까지 이루어지지 않은 경우에 위와 같은 과정을 통해 앱이 재실행되는 시점에 즉시 지급할 수 있습니다. 여기서 verifyDeveloperPayload 메소드가 매우 중요한데 뒤에서 다시 설명해보겠습니다.

 

 

 

 

 

 

 

 

 

각 상품들의 구매 호출시에 실행되는 코드입니다. 각 호출마다 마지막에 payload를 붙이는것을 확인 하실 수 있습니다.

보안팁 : 구매시에 사용되는 이 payload라는 값은 개발자가 지정해주는 임의의 문자열입니다. 여기에 넘겨주는 값이 결제 결과에 다시 그대로 담겨오게 됩니다. 즉 이 값이 구매 직전과 직후에 변동이 있다면 구매 요청에 변조가 있었다고 판단하시면 됩니다. “관리되지 않는 제품”의 경우에도 소진을 하기전까지는 관리가 되므로 소진을 하기전에 변조가 되었는지 여부를 확인하시는 로직을 추가하는것이 중요합니다. 이 예제에서는 verifyDeveloperPayload() 메소드가 그 역할을 하고 있습니다. 소진을 하기전에 게임이 크래시가 날수 있으므로 SharedPreferences등을 활용하여 Persistent하게 저장해두고 진행하는것을 추천합니다. 또는 서버에 developerPayload값을 전달하여 저장하게 하거나 혹은 아예 developerPayload값을 서버로부터 발급받는것도 방법입니다. 이후에 소진하는 시점에 SharedPreferences를 통해 저장한 값을 꺼내쓰거나 서버에 다시 질의하여 사용자가 최종 구매하였던 구매가 완료되지 못한 상품의 developerPayload를 받아오는 식으로 구현하면 될것입니다.

 

 

즉 위와같은 플로우가 될것입니다. 상품의 지급과 소진은 동시에 한시점에 이루어져야 하겠지만 굳이 순서를 정하자면 상품의 소진 이후에 상품을 지급하시기 바랍니다. 만약에 최악의 경우 소진이 이루어지는 직후에 게임이 크래시 난다면 이 부분은 CS에서 처리를 해야 할것입니다.

하지만 수많은 게임이 상품의 지급 정보를 서버에서 수행하므로 (보유 화폐의 증가 등) 원격 서버에서 developerPayload값을 검증을 위해 꺼내간 뒤에 지급이 이루어지지 않은 상태(로그 확인등)에서 유저의 CS가 들어온다면 해당분만큼을 추가 지급처리(보상) 해주면 될것입니다.

 

 

 

 

 

 

 

 

 

 

이 이야기를 결론을 내보자면 궁극적으로 developerPayload의 발급과 검증을 모두 개발사가 보유한 서버에서 진행하는것을 추천합니다. 계속해서 코드 설명을 진행하겠습니다.

 

 

 

 

 

 

 

 

구글 체크아웃을 통해 결제가 성공하면 바로 onActivityResult로 진입하게 됩니다. 헬퍼의 handleActivityResult를 수행하여 구매 요청시에 파라미터로 사용했었던 구매 완료 콜백이 호출됩니다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

구매가 성공한 직후의 콜백의 처리입니다. developerPayload 검증을 진행한 후 “관리되지 않는 제품”을 제외한 나머지는 곧바로 적용을 합니다. 하지만 “관리되지 않는 제품”의 경우 consumeAsync()를 호출하여 소진을 진행합니다.

 

 

 

 

 

 

 

 

 

 

 

 

이 코드는 게임이 최초 실행될 때 실행되었던 queryInventoryAsync() 내에서도 소진할 상품이 있다면 호출되는 코드입니다. 결과를 확인하여 정상적으로 소진이 되었다면 해당 상품의 결과를 적용하면 됩니다.

보안팁 : 이 예제는 게임 클라이언트상에서 상품의 효과를 곧바로 적용하는것을 볼 수 있습니다. 하지만 이러한 상품의 지급은 서버에서 처리하는 것을 권장합니다. developerPayload를 검증하는 시점에 구매한 상품의 정보를 서버에 저장한 뒤에 소진이 성공하면 그 저장된 상품의 정보에 따라 해당 유저의 데이터베이스에 지급을 합니다. 지급이 성공적으로 이루어지면 클라이언트는 다시 서버로부터 최신 데이터를 가져와 화면의 정보를 갱신하도록 구현하면 됩니다.

중요 보안팁 : Google 에서는 상품의 구매 내역을 조회할 수 있는 서버에서 호출 가능한 API를 제공합니다. 패키지명, 상품 아이디, 구매시에 결과로 받았던 구매 토큰을 이용하여 유효한 구매인지를 Google측 서버에 직접 검증할 수 있습니다. 이 API를 사용하여 아이템 지급 전에 실제 유효한 구매였는지를 확인하는것만으로도 해킹의 대부분을 차단할 수 있습니다.

 

 

결과적으로 위와 같은 플로우가 됩니다. 여기서 중요한 핵심은 Google이 제공하는 앱내결제 체크 API를 사용하라는것과 아이템의 실제 지급을 서버에서 처리하라는것입니다. developerPayload의 경우 서버에서 3단계로 확인하는데요, 최초에는 발급하여 서버에 저장만을 하고 두번째에는 정상적으로 검증되었는지 여부를 저장합니다. 마지막 상품 지급 요청에 대하여 developerPayload가 존재하고 검증까지 마친상태인지를 확인하여 맞을 경우 다음 플로우로의 진행을 하게 됩니다. 만약에 developerPayload가 발급된적이 없거나 검증결과가 기록되지 않았다면 불법적인 지급 요청이겠죠.

구글이 제공하는 서버사이드 결제 검증 API를 이용하여 검증하는 방법은 [Android In-App Billing 서버사이드 보안 완벽 정리]를 확인해 주시기 바랍니다.

또한 이러한 과정을 일일이 기록해 두면 유저 CS가 발생시에 보상을 해야 하는지 여부에 대해 판단하는데에 도움이 됩니다. 위의 방법을 기초로 하여 회사의 상황에 맞는 좀 더 복잡한 보안 요소나 대응 방법, 정책등을 결정하여 개발하시면 될것 같습니다.

참고 :

 

 

 

=======================

=======================

=======================

 

 

 

 

출처: http://blog.naver.com/titetoto123/130167699111

   ★★ App 실행시 동작하는 시스템 ★★

 

 

-1. OnCreate 에서 mHelper 의 startSetup 을 호출한다.

 

 

 

 

-2. labHelper 의 startSetup 에서 ServiceConnection ( onServiceDisconnected,  onServiceConnected 포함)

     인스턴스를 생성한 후

        Intent serviceIntent = new Intent("com.android.vending.billing.InAppBillingService.BIND");

        if (!mContext.getPackageManager().queryIntentServices(serviceIntent, 0).isEmpty()) {

            // service available to handle that Intent

            mContext.bindService(serviceIntent, mServiceConn, Context.BIND_AUTO_CREATE);

 

       을 통해서  InAppBillingService를  사용 할 수 있도록 bindService 메소드를 호출한다.

 

 

 

 

-3.  InAppBillingService  bind 요청을 하면 비동기 응답으로

      onServiceConnected() 가 호출된다.

 

            public void onServiceConnected(ComponentName name, IBinder service) {

                logDebug("Billing service connected.");

                mService  = IInAppBillingService.Stub.asInterface(service);

 

        여기서 IInAppBillingService 를 사용할 수 있는 인스턴스를 얻는다.

 

 

 

 

-4.  IInAppBillingService 인스턴스를 받으면   packageName과 api가  in_app 결제를 할 수 있는지

       isBillingSupported 메소드를 통해서 요청할 수 있다.

     ( inapp :  영구, 비영구 아이템

       sub     :   구독 결제 )

 

 

 

 

-5. 결제를 할 수 있는지 확인이 끝나면

            public void onServiceConnected(ComponentName name, IBinder service) {

      함수 안에서

           listener.onIabSetupFinished(new IabResult(BILLING_RESPONSE_RESULT_OK, "Setup successful."));

      를 통해서 MainActivity에 onIabSetupFinished() 함수를 호출한다.

 

 

 

 

-6.  queryInventoryAsync 에서 queryPurchases(소유한 아이템들을 가져온다) 를 호출한다.

    

      ---- queryInventory() 메소드   ---

     queryPurchases() 함수를 호출해 getPurchases() 메소드를 사용해서 소유한   상품들을 inventory에 넣는다.

    (★★ 중요 ★★ :  소유한 아이템들을 가져오는 것이다 . (소비되지 않은)  )

 

     querySkuDetails() 함수를 호출해 getSkuDetails() 메소드를 호출해 결제한 ID 에 대한 상세 정보를 가져온다.

 

 

 

 

-7.  결제한 정보들에 대한 Query가 끝나면 MainActivity의  onQueryInventoryFinished() 메소드가 호출된다.

       해당 메소드에서  소비되지 않는 Item들을  consume 시켜준다.     

 

 

 

 

 

 

 

       ★★ 구입 버튼 눌렀을시 로직 ★★

 

 

-1. labHelper의 launchPurchaseFlow 를 호출한다.

 

 

 

-2. getBuyIntent 를 호출하면  반환값으로 Bundle을 주는데

그 안에는 결제창을 띄우는 PendingIntent와  Builling_RESPONSE_RESULT_OK 를 준다.

 

 

 

 

-3.  buyIntentBundle.getParcelable(RESPONSE_BUY_INTENT) 를 호출해

       PendingItent를 얻어와 결제창을 띄우자.

       PendingIntent를 얻어와

      MainActivity 의  startIntentSenderForResult()  로  결제창을 띄운다.

 

 

 

-4.  결제창이 뜨고  결제가되거나  취소가 되면

       MainActivity의  

protected void onActivityResult(int requestCode, int resultCode, Intent data) {

      가 호출된다.

      intent date 에는 결제한 item의 정보가 들어있다 (json)

 

(ex)

{ 
   "orderId":"12999763169054705758.1371079406387615", 
   "packageName":"com.example.app",
   "productId":"exampleSku",
   "purchaseTime":1345678900000,
   "purchaseState":0,
   "developerPayload":"bGoa+V7g/yqDXvKRqq+JTFn4uQZbPiQJo4pf9RzJ",
   "purchaseToken":"rojeslcdyyiapnqcynkjyyjh"
 }'

 

 

 

 

-5. 그런다음 handleActivityResult() 에서

      구매 Item 에 관한 정보를 출력해준다.

 

 

 

 

-6.  그 다음 결제한 아이템에 대해  Security로  signature를 확인한다. (서버에서 해줘야 한다.)

 

 

 

-7.  결제할때 등록해놨던 Listener   mPurchaseListenr의  onIabPurchaseFinished에

      성공했다는 호출을 한다.

      해당 함수에서 아이디를 비교한다.

 

 

-8. 마지막으로, 구글은 모든 인앱 제품에 대하여 관리를 하는데,

      구입을 하면 구글 플레이에 기록되 해당 제품은 "소유" 로 간주된다.

      따라서, 소모성 아이템의 경우  consumePurchase 를 호출해서

      해당 아이템이 소비되었다는 호출을 해서 다시 구입 할 수 있게 해주어야 한다. 따라서,

      mHelper.consumeAsync() 호출.

 

 

 

 

-9.. consumePurchase() 를 호출하고  성공했다는 응답이 오면

      onConsumeFinished()  콜백 함수가 호출된다.

      따라서, 비소모성 아이템이 구글 플레이에서 "소유" 에서 소비가 되었으므로

     다시 구입할 수 있다.     

 

 

 

=======================

=======================

=======================

 

 

 

반응형


관련글 더보기

댓글 영역