본문 바로가기

안드로이드

[Android] 인앱결제 예제 (BillingClient 사용) - 관리되는 제품 코드

* 해당 글을 참고하기 전에 해당 앱에 '관리되는 제품' 등록되어 있어야 한다.

참고 https://puch-android.tistory.com/4

 

* 아래 글을 참고하면서, 구글에서 제공하는 문서도 참고하면 이해하는데 도움이 된다.

참고 https://developer.android.com/google/play/billing/billing_library_overview?hl=ko

 

 

 

안드로이드 인앱결제 - 관리되는 제품 코드 (이하 아이템)

 

public class BillingEntireManager implements PurchasesUpdatedListener {

 

선언부

private BillingClient mBillingClient;

// 아이템 상세정보 리스트
private List<SkuDetails> mSkuDetailsList_item;

// 아이템 소비 리스너
private ConsumeResponseListener mConsumeListener;

 

초기화

public BillingEntireManager(Context ctx) {
    this.ctx = ctx;
    Log.d(TAG, "구글 결제 매니저를 초기화 하고 있습니다.");
    
    mBillingClient = BillingClient.newBuilder(ctx).setListener(this).enablePendingPurchases().build();
    mBillingClient.startConnection(new BillingClientStateListener() {
        @Override
        public void onBillingSetupFinished(BillingResult billingResult) {
            if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
                Log.d(TAG, "구글 결제 서버에 접속을 성공하였습니다.");
                getSkuDetailList();
                
                // 아이템 결제 히스토리 가져옴
                Purchase.PurchasesResult result_item = mBillingClient.queryPurchases(BillingClient.SkuType.INAPP);
                
                // 소모가 안된 상품 존재시 - 리스트에 아이템이 존재하게 된다
                List<Purchase> list_item = result_item.getPurchasesList();
                
                for (int i = 0; i < list_item.size(); i++) {
                    Purchase purchase = list_item.get(i);
                    // 계속 보류중일때
                    if (purchase.getPurchaseState() != Purchase.PurchaseState.PURCHASED) {
                        // 카드사 승인중인 결제 또는 결제 보류중
                    } else {
                        // 결제 승인된 경우
                        handlePurchase(list_item.get(i));
                    }
                }
            } else {
                Log.d(TAG, "구글 결제 서버 접속에 실패하였습니다.\n오류코드: " + billingResult.getResponseCode());
                // case 구글 플레이스토어 계정 정보 인식 안될 때
            }
        }
        
        @Override
        public void onBillingServiceDisconnected() {
            Log.d(TAG, "구글 결제 서버와 접속이 끊어졌습니다.");
        }
    });
    
    // 상품 소모결과 리스너
    mConsumeListener = new ConsumeResponseListener() {
        @Override
        public void onConsumeResponse(BillingResult billingResult, String purchaseToken) {
            if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
                Log.d(TAG, "상품을 성공적으로 소모하였습니다. 소모된 상품 => " + purchaseToken);
                return;
            } else {
                Log.d(TAG, "상품 소모에 실패하였습니다. 오류코드 (" + billingResult.getResponseCode() + "), 대상 상품 코드: " + purchaseToken);
                return;
            }
        }
    };
}

 

상품 리스트 가져오는 메소드

- Sku 리스트에 추가되는 아이템 ID  본인이 콘솔에서 아이템 추가할때, 기재한 ID를 넣어야 한다.

// 구입 가능한 상품의 리스트를 받아 오는 메소드
private void getSkuDetailList() {
    // 구글 상품 정보들의 ID를 만들어 줌
    List<String> Sku_ID_List_INAPP = new ArrayList<>();
    
    Sku_ID_List_INAPP.add(POINT_01_CODE);
    Sku_ID_List_INAPP.add(POINT_02_CODE);
    Sku_ID_List_INAPP.add(POINT_03_CODE);
    Sku_ID_List_INAPP.add(POINT_04_CODE);
    Sku_ID_List_INAPP.add(POINT_05_CODE);
    Sku_ID_List_INAPP.add(POINT_06_CODE);
    
    // SkuDetailsList 객체를 만듬
    SkuDetailsParams.Builder params_item = SkuDetailsParams.newBuilder();
    params_item.setSkusList(Sku_ID_List_INAPP).setType(BillingClient.SkuType.INAPP);
    
    // 비동기 상태로 앱의 정보를 가지고 옴
    mBillingClient.querySkuDetailsAsync(params_item.build(), new SkuDetailsResponseListener() {
        @Override
        public void onSkuDetailsResponse(BillingResult billingResult, List<SkuDetails> skuDetailsList) {
            // 상품 정보를 가지고 오지 못한 경우
            if (billingResult.getResponseCode() != BillingClient.BillingResponseCode.OK) {
                Log.d(TAG, "(인앱) 상품 정보를 가지고 오던 중 오류가 발생했습니다.\n오류코드: " + billingResult.getResponseCode());
                return;
            }
            if (skuDetailsList == null) {
                Log.d(TAG, "(인앱) 상품 정보가 존재하지 않습니다.");
                return;
            }
            //응답 받은 데이터들의 숫자를 출력
            Log.d(TAG, "(인앱) 응답 받은 데이터 숫자: " + skuDetailsList.size());
            
            //받아온 상품 정보를 차례로 호출
            for (int sku_idx = 0; sku_idx < skuDetailsList.size(); sku_idx++) {
                //해당 인덱스의 객체를 가지고 옴
                SkuDetails _skuDetail = skuDetailsList.get(sku_idx);
                //해당 인덱스의 상품 정보를 출력
                Log.d(TAG, _skuDetail.getSku() + ": " + _skuDetail.getTitle() + ", " + _skuDetail.getPrice());
                Log.d(TAG, _skuDetail.getOriginalJson());
            }
            
            //받은 값을 멤버 변수로 저장
            mSkuDetailsList_item = skuDetailsList;
        }
    });
}

 

 

실제 구입요청하는 메소드

//실제 구입 처리를 하는 메소드
public void purchase(String item, Activity act) {
    SkuDetails skuDetails = null;
    if (null != mSkuDetailsList_item) {
        for (int i = 0; i < mSkuDetailsList_item.size(); i++) {
            SkuDetails details = mSkuDetailsList_item.get(i);
            if (details.getSku().equals(item)) {
                skuDetails = details;
                break;
            }
        }
        
        BillingFlowParams flowParams = BillingFlowParams.newBuilder()
                .setSkuDetails(skuDetails)
                .build();
        mBillingClient.launchBillingFlow(act, flowParams);
    }
}

 

결제에 대해 소비시켜주는 함수

// 결제 요청 후 상품에대해 소비시켜주는 함수
void handlePurchase(Purchase purchase) {
    if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED) {
    
        // 인앱 소비
        // TODO 인앱 구매 결과전송 함수 호출
        ConsumeParams consumeParams =
                ConsumeParams.newBuilder()
                        .setPurchaseToken(purchase.getPurchaseToken())
                        .build();
        mBillingClient.consumeAsync(consumeParams, mConsumeListener);
        
    } else if (purchase.getPurchaseState() == Purchase.PurchaseState.PENDING) {
        // Here you can confirm to the user that they've started the pending
        // purchase, and to complete it, they should follow instructions that
        // are given to them. You can also choose to remind the user in the
        // future to complete the purchase if you detect that it is still
        // pending.
        // ex 해당 아이템에 대해 소모되지 않은 결제가 있을시
    }
}

 

 

결제 성공여부 리스너

@Override
public void onPurchasesUpdated(BillingResult billingResult, @Nullable List<Purchase> purchases) {
    //결제에 성공한 경우
    if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK && purchases != null) {
        Log.d(TAG, "결제에 성공했으며, 아래에 구매한 상품들이 나열됨");
        for (Purchase _pur : purchases) {
            Log.e(TAG, "purchases: " + purchases);
            handlePurchase(_pur);
        }
    }
    
    // 사용자가 결제를 취소한 경우
    else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.USER_CANCELED) {
        Log.d(TAG, "사용자에 의해 결제취소");
    }
    
    // 그 외에 다른 결제 실패 이유
    else {
        Log.d(TAG, "결제가 취소 되었습니다. 종료코드: " + billingResult.getResponseCode());
        if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED) {
            // ex 해당 아이템에 대해 소모되지 않은 결제가 있을시
        }
    }
}