본문 바로가기
Android/안드로이드 스터디(JAVA)

Android Studio, Java] 구글 인앱결제 적용하기(1회성 소모결제)

by 김마리님 2022. 5. 18.

Kotiln 구현은 이쪽으로 <<

https://itstudy-mary.tistory.com/399?category=961265 

 

Android, Kotlin] 구글 인앱결제 적용하기(1회성 소모결제)

(자바도 쓰긴 할건데 언제가 될진) 6/1일부터 구글에서 결제 우회하면 앱을 내릴거라는 경고가 왔다고, 급히 인앱결제를 추가해달라는 요청이 왔다. 근데 여기서 주의해야할 점이 있다. 왜냐면

itstudy-mary.tistory.com

 

코틀린에서는 상속을 받는 방식으로 구현해보았지만, 자바는 상속을 받지 않고 리스너를 정의해서 적용하는 방식으로 해보았다.

상속 구현은 코틀린에 있으니 그쪽과 비교해가면서 해보길 바란다.

그리고 자바에서는 인터페이스를 사용해보았다. 유틸이기 때문에 이 결제가 성공했는지 안했는지 콜백을 위한 인터페이스라고 봐도 무방하다.

 

    public interface GooglepayJavaUtilDelegate {
        void onSuccess();
    }
    
    private GooglepayJavaUtilDelegate GooglepayJavaUtilDelegate;

    public GooglepayJavaUtil(@NotNull GooglepayJavaUtil.GooglepayJavaUtilDelegate GooglepayJavaUtilDelegate) {
        this.GooglepayJavaUtilDelegate = GooglepayJavaUtilDelegate;
    }

 

먼저 인터페이스를 구현한다. 

인터페이스 작성 후 전역변수로 인터페이스를 선언하고 생성자를 이용해 외부에서 해당 인터페이스를 선언 해 전달할 수 있도록 한다. 이렇게 되면 필요한 액티비티에서 유틸 호출 시

        GooglepayUtil.GooglepayUtilDelegate googlepayUtilDelegate = () -> ILog.iLogDebug(TAG, "onSuccess");
        googlepayUtil = new GooglepayUtil(googlepayUtilDelegate);

 

해당 방식으로 인터페이스를 호출 후 오버라이딩 하여 인터페이스에 대한 동작을 구현할 수 있다.

 

먼저 전역변수를 선언한다.

이 때, 후에 쓰이게 될 업데이트(결제 후 서버 통신 콜백 매서드), 소비 완료 콜백 매서드를 담당할 리스너를 함께 선언해둔다.

    private BillingClient billingCilent;
    private List<ProductDetails> productDetailsList;
    private GooglepayJavaUtilDelegate GooglepayJavaUtilDelegate;
    
    //결제 완료 후 서버통신 콜백 매서드
    private final PurchasesUpdatedListener purchaseUpdateListener = new PurchasesUpdatedListener() {
        @Override
        public void onPurchasesUpdated(@NonNull BillingResult billingResult, @Nullable @org.jetbrains.annotations.Nullable List<Purchase> list) {
            if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK && list != null) {
                for (Purchase purchase : list) {
                    DlogUtil.INSTANCE.d(TAG, "서버 통신 완료");

                    //소비로직(정합성 확인)
                    ConsumeParams consumeParams = ConsumeParams.newBuilder()
                            .setPurchaseToken(purchase.getPurchaseToken())
                            .build();

                    billingCilent.consumeAsync(consumeParams, consumeListenser);
                }
            }
        }
    };
    
    //1회성 결제 아이템 소비콜백 매서드
 	private ConsumeResponseListener consumeListenser = (billingResult, s) -> {
            if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
                DlogUtil.INSTANCE.d(TAG, "소모 성공");
                if(GooglepayJavaUtilDelegate != null) {
                    GooglepayJavaUtilDelegate.onSuccess();
                }
            } else {
                DlogUtil.INSTANCE.d(TAG, "소모 실패");
            }
        };

UpdateListener의 경우 결제 화면이 내려가면 호출되는데, 이 부분은 서버통신에 대한 콜백만을 뱉기 때문에  후에 결제가 거부가 일어난다 하더라도 Response OK 를 내려버린다. 그렇기 때문에 소비로직은 1회성 제품을 소비시켜 지속적인 소비를 유도하는 것도 있지만 결제가 정상으로 일어났는지 consumeListener에서 정합성을 확인하는데에도 사용된다. 따라서, OK가 온다면 comsumeParma을 생성하여 내부에 결제토큰을 실어 소비를 시도한다. 이 결제가 거부되거나 Pending중이라면 해당 토큰이 소비되지 않을것이므로 consumeListener billingResult != 0이 리턴되며 에서 소모 실패를 반환한다. 

 

 

구글 서버와의 통신을 담당하는 인터페이스부터 먼저 선언하자.

   public void initBillingClient(Context context) {

        billingCilent = BillingClient.newBuilder(context)
                .setListener(purchaseUpdateListener)
                .enablePendingPurchases()
                .build();

        billingCilent.startConnection(new BillingClientStateListener() {
            @Override
            public void onBillingServiceDisconnected() {
                DlogUtil.INSTANCE.d(TAG, "연결 실패");
                handleBillingClientDisconnection(context);
            }

            @Override
            public void onBillingSetupFinished(@NonNull BillingResult billingResult) {
                DlogUtil.INSTANCE.d(TAG, "연결 성공");
                getPurchaseList();
            }
        });

        consumeListenser = (billingResult, s) -> {
            if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
                DlogUtil.INSTANCE.d(TAG, "소모 성공");
                if(GooglepayJavaUtilDelegate != null) {
                    GooglepayJavaUtilDelegate.onSuccess();
                }
            } else {
                DlogUtil.INSTANCE.d(TAG, "소모 실패");
            }
        };

    }

 

여기서 말하는 purchaseUpdatedListener은 앞서 선언한 서버 콜백 매서드이다.

 

인터페이스 설정이 끝나면 결제 빌드를 위한 결제 가능 목록을 호출한다. 여기서 불러온 데이터 중 중 원하는 상품으로 목록을 재설정하여 최종적으로 결제를 요청하게 된다(이 과정이 필요한 이유가.. 여기서 오는 콜백 자료형으로 결제 요청을 하기 때문입니다.)

    private void getPurchaseList() {

        List<QueryProductDetailsParams.Product> listParam = new ArrayList<>();

        if(itemIdList.length > 0) {
            for(String itemId : itemIdList) {
                listParam.add(
                        QueryProductDetailsParams.Product.newBuilder()
                                .setProductId(itemId)
                                .setProductType(BillingClient.ProductType.INAPP)
                                .build()
                );
            }
        } else {
            DlogUtil.INSTANCE.d(TAG, "no list");
            return;
        }

        QueryProductDetailsParams itemParam = QueryProductDetailsParams.newBuilder()
                .setProductList(listParam)
                .build();

        billingCilent.queryProductDetailsAsync(itemParam, new ProductDetailsResponseListener() {
            @Override
            public void onProductDetailsResponse(@NonNull BillingResult billingResult, @NonNull List<ProductDetails> list) {
                DlogUtil.INSTANCE.d(TAG, list);
                productDetailsList = list;
            }
        });

    }

먼저 우리가 사용할 구글인앱결제 id(콘솔에서 등록 하는 것이며, 콘솔에서 확인 가능합니다.)를 통해 Param의 제품을 빌드하고, 이 제품의 목록을 바탕으로 queryProductDetailAsync를 통해 리스트로 들어온 아이디에 부합하는 제품의 세부사항 리스트를 요청합니다.

billingResult == 0  --> billingClient.BillingResponseCode.OK와 동일하다면 리스트를 정상적으로 리턴합니다.

 

이제 이 리스트를 가지고 결제를 시도합니다.

    public void callPay(@NotNull Activity activity, @NotNull String productId) {
        List<BillingFlowParams.ProductDetailsParams> list = new ArrayList<>();

        for(ProductDetails productDetail : productDetailsList) {
            if(productDetail.getProductId().equals(productId)) {
                BillingFlowParams.ProductDetailsParams productDetailsParams = BillingFlowParams.ProductDetailsParams.newBuilder()
                        .setProductDetails(productDetail)
                        .build();
                list.add(productDetailsParams);
            }
        }

        BillingFlowParams flowParams = BillingFlowParams.newBuilder()
                .setProductDetailsParamsList(list)
                .build();

        billingCilent.launchBillingFlow(activity, flowParams);
    }

먼저 제품의 DetailParams를 생성하여 리스트화 합니다. 이렇게 결제를 요청할 최종 리스트를 작성합니다. 

그리고 BillingFlowParam을 통해 최종 결제를 요청합니다.

반응형