프로그래밍/Android

[2019년] 안드로이드 인앱 결제 구현 초간단 정리!

Lou Park 2019. 3. 7. 16:04

* 2018년 안드로이드 결제 구현에서 다이얼로그 목록으로 결제를 구현하는 부분때문에 많은 분들이 헷갈려하셨고,

앱도 내 앱이아니라 소스 코드를 완벽하게 공개하기 어려웠는데, 그래서 2019년! 

초간단하게 안드로이드 결제를 구현하는 방법을 정리해서 다시 올린다.

(간단명료에 초점을 맞췄으므로 각 메소드에 대한 자세한 설명은 2018년 글을 참조 부탁드립니다.)


- 광고제거 버전 구현


목차 

: Android In-app Billing 101~

 

1. 구글 플레이 콘솔 설정

- 1) 구글 플레이 콘솔에 앱 생성하기

- 2) 라이센스키 얻기

- 3) 인앱상품 등록하기


2. 안드로이드 개발시 설정

- 1) build.gradle에 라이브러리 implement하기

- 2) AndroidManifest.xml에 BILLING 권한 추가하기

- 3) 액티비티에 코드 작성





1-1) 구글 플레이 콘솔 설정 > 구글 플레이 콘솔에 앱 생성하기

앱 생성은 너무 쉬우니 생략!



구글 플레이 콘솔에 앱을 생성한 후 앱 버전> 프로덕션 트랙 관리> 출시를 눌러 Google에서 앱 서명 키를 관리 및 보호하도록 허용 > 계속을 눌러준다.

그러면 아래와 같이 "사용 중입니다." 표시가 뜨게된다.



1-2) 구글 플레이 콘솔 설정 > 라이센스키 얻기



위 과정을 끝내면 좌측에 서비스 및 API 탭이 생겼을 것이다.

클릭해서 라이센스키 Base64 코드를 복사해 앱 어딘가에 저장해둔다.


1-3) 인앱상품 등록하기



* 인앱상품 등록을 위해서는 APK 등록이 먼저 필요한데, 이 과정은 안드로이드 코드 구현 후 나중에 해도 된다.

관리되는 제품을 추가한다.

위에 보면 이름 및 ID란에 괄호 안에 있는 코드가 있을텐데 (ex) donate/pro_version/library_slot_10)

이것을 Sku라고 하겠다. 강의에서는 광고 제거 버전을 만들것이고, sku는 no_ads 로 할것이다.



2-1) 안드로이드 개발시 설정 > build.gradle

app 단계의 build.gradle에 결제 라이브러리를 추가한 후 Sync한다.

1
implementation 'com.anjlab.android.iab.v3:library:1.0.44'
cs


2-2) 안드로이드 개발시 설정 > AndroidManifest.xml

BILLING 권한을 추가한다.

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.geumson.utjjal">
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="com.android.vending.BILLING" />
    <application
        ...
    </application>
</manifest>
cs



2-3) 안드로이드 개발시 설정 > SomeActivity.java

이제 결제를 진행할 Activity에서 설정을 시작할 것이다.

예제에서는 SettingsActivity.java에서 광고 제거를 구매하는 로직을 넣을것이다!

뷰는 아래와 같이 생겼고, 광고 없이 앱 이용하기 버튼의 id는 btn_no_ads이다.




먼저 Activity에 BillingProcessor를 implement한다.


1
public class SettingsActivity extends AppCompatActivity implements BillingProcessor.IBillingHandler
cs


그러면 관련 메소드 4종이 나타나게된다.

주석으로 간략하게 설명해두었다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
    @Override
    public void onProductPurchased(@NonNull String productId, @Nullable TransactionDetails details) {
        // * 구매 완료시 호출
        // productId: 구매한 sku (ex) no_ads)
        // details: 결제 관련 정보
    }
 
    @Override
    public void onPurchaseHistoryRestored() {
        // * 구매 정보가 복원되었을때 호출
        // bp.loadOwnedPurchasesFromGoogle() 하면 호출 가능
    }
 
    @Override
    public void onBillingError(int errorCode, @Nullable Throwable error) {
        // * 구매 오류시 호출
        // errorCode == Constants.BILLING_RESPONSE_RESULT_USER_CANCELED 일때는
        // 사용자가 단순히 구매 창을 닫은것임으로 이것 제외하고 핸들링하기.
    }
 
    @Override
    public void onBillingInitialized() {
        // * 처음에 초기화됬을때.
    }
cs


그리고 결제를 도와줄 BillingProcessor 객체를 만들어야하는데 코드는 다음과 같다.

onDestroy(), onActivityResult()도 까먹지말고 구현하기~!

Config.GooglePlayLicenseKey에서는 이전에 구한 구글플레이 라이센스키를 넣으면 된다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class SettingsActivity extends AppCompatActivity implements BillingProcessor.IBillingHandler, View.OnClickListener {
    private BillingProcessor bp;
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_settings);
        ButterKnife.bind(this);
 
        bp = new BillingProcessor(this, Config.GooglePlayLicenseKey, this);
        bp.initialize();
 
    }
 
    ...
 
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (!bp.handleActivityResult(requestCode, resultCode, data)) {
            super.onActivityResult(requestCode, resultCode, data);
        }
    }
 
    @Override
    public void onDestroy() {
        if (bp != null) {
            bp.release();
        }
        super.onDestroy();
    }
}
 
cs


bp를 통해서 구매정보를 파악하고, 구매를 시킬수도 있지만

모든 Activity에 BillingProcessor를 implement할 수는 없으니(귀찮아서) 나는 구매 정보를 업데이트하고 저장하기 위한 SharedPreferences를 다루는 객체를 사용할것이다. 이 부분은 필수는 아니지만 혹시 코드 진행상 헷갈리실까봐 공개한다.

AppStorage는 사용자의 구매여부를 저장해뒀다가 bp를 불러온 SettingsActivity를 제외한 나머지 Activity에서도 사용자의 광고제거 구매 여부에따라 광고를 보여줄지, 숨길지 결정하는데 도움을 준다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class AppStorage {
    private Context context;
    private SharedPreferences pref;
    private String PURCHASED_REMOVE_ADS = "remove_ads";
 
    public AppStorage(Context context) {
        pref = context.getSharedPreferences("app_storage", Context.MODE_PRIVATE);
        this.context = context;
    }
 
    public boolean purchasedRemoveAds() {
        return pref.getBoolean(PURCHASED_REMOVE_ADS, false);
    }
 
    public void setPurchasedRemoveAds(boolean flag) {
        SharedPreferences.Editor editor = pref.edit();
        editor.putBoolean(PURCHASED_REMOVE_ADS, flag);
        editor.apply();
    }
}
 
cs


나머지 구매를 위한 코드는 다음과같다.

앞에서 말했듯 광고 제거 구매버튼을 누를때 구매를 수행한다. (주석을 잘보면 도움이 되실거에요)


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
public class SettingsActivity extends AppCompatActivity implements BillingProcessor.IBillingHandler, View.OnClickListener {
    private BillingProcessor bp;
    private AppStorage storage;
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_settings);
        ButterKnife.bind(this);
        AppStorage storage = new AppStorage(this);
        bp = new BillingProcessor(this, Config.GooglePlayLicenseKey, this);
        bp.initialize();
 
    }
 
    // 광고 없이 이용하기
    @OnClick(R.id.btn_no_ads)
    public void clickBtnNoAds(View v) {
        v.startAnimation(Animator.getClickAnimation(this));
        if (storage.purchasedRemoveAds()) {
            // TODO: 이미 구매하셨습니다. 메세지 띄우기!
        } else {
            bp.purchase(this, Config.Sku);
        }
    }
 
    @Override
    public void onProductPurchased(@NonNull String productId, @Nullable TransactionDetails details) {
        // 이 메소드는 구매 '성공'시에만 호출된다.
        if (productId.equals(Config.Sku)) {
            // TODO: 구매 해 주셔서 감사합니다! 메세지 보내기
            storage.setPurchasedRemoveAds(bp.isPurchased(Config.Sku));
            
            // * 광고 제거는 1번 구매하면 영구적으로 사용하는 것이므로 consume하지 않지만,
            // 만약 게임 아이템 100개를 주는 것이라면 아래 메소드를 실행시켜 다음번에도 구매할 수 있도록 소비처리를 해줘야한다.
            // bp.consumePurchase(Config.Sku);
        }
    }
 
    @Override
    public void onPurchaseHistoryRestored() {
        storage.setPurchasedRemoveAds(bp.isPurchased(Config.Sku));
    }
 
    @Override
    public void onBillingError(int errorCode, @Nullable Throwable error) {
        /*
        TODO: 이런식으로 구매 오류시 오류가 발생했다고 알려주는 것도 좋다.
        if (errorCode != Constants.BILLING_RESPONSE_RESULT_USER_CANCELED) {
            Snackbar.make(tvRemoveAds, R.string.unknown_error, Snackbar.LENGTH_SHORT).show();
        }
         */
    }
 
    @Override
    public void onBillingInitialized() {
        // storage에 구매여부 저장
        storage.setPurchasedRemoveAds(bp.isPurchased(Config.Sku));
    }
 
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (!bp.handleActivityResult(requestCode, resultCode, data)) {
            super.onActivityResult(requestCode, resultCode, data);
        }
    }
 
    @Override
    public void onDestroy() {
        if (bp != null) {
            bp.release();
        }
        super.onDestroy();
    }
}
 
cs


이제 다음 액티비티에서는 단순히 AppStorage만을 불러와서 purchasedRemoveAds() 메소드를 통해 구매를 했는지 안했는지 가려가며 코딩하면된다.


* 참, 구매 테스트는 안드로이드 스튜디오나 apk파일이 아닌 콘솔에 알파버전 이상에 올린 것으로만 가능하니 참고해주세요!


댓 글 환 영


구독 구현도 궁금하시다면 댓글 달아주시면 5개의 댓글이 쌓이면 준비하도록 하겠습니다!