안녕하세요 여러분!!
오늘은 수익형 앱을 만들기 위해 거의~ 필수적으로 필요한 구글 인앱결제에 대해 알아보도록 할게요~
일단 플레이스토어 콘솔에서 상품을 등록하는 부분을 제외하겠습니다.
그 다음 소스코드를 짜는것 부터 포스팅해요~
당연히 소스코드를 제공하겠죠.
물론 안드로이드 개발자 가이드에 설명이 돼있지만, 아시다 시피 너무 불친절 하잖아요.
저는 그래서 주석을 친절하게 달아주어서 어디에 뭘 써야 하는지 다 알려드릴게요~
제가 로그 한줄한줄 마다 하면서 공부한 내용이니까 궁금하시면 읽어봐 보세요~
코드를 어느정도 공부하신 분들은 아시겠지만, 결제 서비스를 한 화면에만 적용하게 하는 법이있고,
캡슐화(class) 시켜서 어디서든 선언하여 쓸 수 있게 하는 방법이 있죠.
당연히 전 캡슐화(class)로 만들어서 쓰기 때문에 그 소스코드로 알려드릴게요.
일단 제일 먼저
build.gradle ( 앱) 에
dependencies{
def billing_version = "4.0.0"
implementation "com.android.billingclient:billing:$billing_version"
}
를 추가해 줍니다. 기본이죠~?
그리고 뭐 menifest에 필요한 권한이나 이런건 알아서 해주시면 되구요~
자 이제 결재 서비스 Class를 만들어 볼게요.
import android.app.Activity;
import android.content.Context;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.billingclient.api.BillingClient;
import com.android.billingclient.api.BillingClientStateListener;
import com.android.billingclient.api.BillingFlowParams;
import com.android.billingclient.api.BillingResult;
import com.android.billingclient.api.ConsumeParams;
import com.android.billingclient.api.ConsumeResponseListener;
import com.android.billingclient.api.Purchase;
import com.android.billingclient.api.PurchasesUpdatedListener;
import com.android.billingclient.api.SkuDetails;
import com.android.billingclient.api.SkuDetailsParams;
import com.android.billingclient.api.SkuDetailsResponseListener;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
import static android.content.ContentValues.TAG;
import static com.coin.whichfood.MainActivity.activity;
//클래스 선언
public class BillingService implements PurchasesUpdatedListener{
//각 주요 결재 변수들을 선언해 줍니다.
private BillingClient billingClient;
private List<SkuDetails> skuDetails_list;
private ConsumeResponseListener consumeResponseListener;
private Context context;
//이부분이 제일 중요한 부분입니다. 저음 BillingServie(클래스이름과 똑같이)를 선언하여 초기화를 해줍니다.
//안드로이드 개발자 가이드 에는 그냥 떵그러니 이 함수만 나와있습니다. 어디에 뭘 넣아야하는지 안알려주구요.
public BillingService(Context context) {
this.context = context;
billingClient = BillingClient.newBuilder(context)
.setListener(this::onPurchasesUpdated)//이부분은 밑에 선언된 구매시 구매완료 및 실패에 대한 콜백을 해주는 부분입니다.
.enablePendingPurchases()
.build();
//billingClient.startConnection 은 결제 이벤트를 실행했을때, 결제사이트에 접속하는 과정이라 보시면 됩니다.
billingClient.startConnection(new BillingClientStateListener() {
@Override
public void onBillingSetupFinished(BillingResult billingResult) {
//if문이 접속이 됐냐 안됐냐 에 따른 이벤트 처리입니다.
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
// The BillingClient is ready. You can query purchases here.
Log.d(TAG, "biilingcount2");
//접속이 됫을때 구매목록을 보여달라는 이벤트를 넣엇습니다.
getSkuDetailList(); // 이 함수도 밑에 있습니다.
//안드로이드 개발자 가이드에는 이걸 이렇게 넣어서 쓰라는 정보가 없습니다.
//한동안 어떻게 쓰는지 찾느라 로그 노가다를 했습니다.
}
}
@Override
public void onBillingServiceDisconnected() {
//이건 연결이 해제되거나, 연결되지 안았을때 이벤트 입니다.
// 본인이 원하는 이벤트나 오류 표출문구를 넣어주세요.
// Try to restart the connection on the next request to
// Google Play by calling the startConnection() method.
}
});
// 상품 소모결과 리스너
//이부분은 상품을 구매하거나 했을때 실행되는 함수 입니다.
//상품을 구매하거나 하면 purchaseToken에 값이 저장이 되는데 그것을 보여줍니다.
consumeResponseListener = 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;
}
}
};
}
// 즉, 처음 BillingService를 초기화 하면, 구글창에 연결을 하고 구매리스트를 변수에저장합니다.
// 그리고 현재 무언가 구매한 상태라면 그 마지막 구매 목록을 불러와 보여줍니다
// 안드로이드 개발자 가이드에 정보를 보면서 같이 보면 이해가 빠를거에요.
//결제과 완료 됐을때 나오는 문구 입니다. 함수멸을 보시다 시피 결제 업데이트 입니다.
//윗부분 billingClient.setListner(this::onPurchaseUpdated) 에 사용되는 함수입니다.
//콜백과 비슷한 기능입니다. 42번쨰 줄
@Override
public void onPurchasesUpdated(@NonNull @NotNull BillingResult billingResult, @Nullable @org.jetbrains.annotations.Nullable List<Purchase> purchases) {
Log.d(TAG,"biilingcount1");
// To be implemented in a later section.
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK
&& purchases != null) {
Log.d(TAG, "결제에 성공했으며, 아래에 구매한 상품들이 나열됨");
for (Purchase purchase : purchases) {
Log.e(TAG, "결제 구매완료상품: " + purchases);
handlePurchase(purchase);
}
} else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.USER_CANCELED) {
// Handle an error caused by a user cancelling the purchase flow.
} else {
// Handle any other error codes.
}
}
//구매 목록을 변수에 저장하고, 실제 구글상품 목록을 가져와 비교합니다.
//사실 따로 변수를 선언해서 저장할 필요까지 없으나, 심적안정을 위해 상품목록 확인을 해주고
//그 목록을 리스트에 저장해줍니다.
//이때 skuList.add 에는 플레이스토어 콘설에서 등록한 결제 상품의 id를 입력해주시면 됩니다.
//이 함수도 위쪽에 구글결제에 연결이 완료 됐을때, 선언된 함수입니다. 56번재 줄
public void getSkuDetailList() {
Log.d(TAG, "biilingcount3");
List<String> skuList = new ArrayList<>();
skuList.add("VIP정기구독");
skuList.add("VVIP정기구독");
SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder();
params.setSkusList(skuList).setType(BillingClient.SkuType.SUBS);//.INAPP은 일회용 결제고, SUBS는 구독형 결제에요~
billingClient.querySkuDetailsAsync(params.build(),
new SkuDetailsResponseListener() {
@Override
public void onSkuDetailsResponse(BillingResult billingResult,
List<SkuDetails> skuDetailsList) {
Log.d(TAG, "biilingcount3.1");
if (billingResult.getResponseCode() != BillingClient.BillingResponseCode.OK) {
Log.d(TAG, "biilingcount3.2");
return;
}
Log.d(TAG, "biilingcount3.3");
//상품정보를 가저오지 못함 면 동작 이벤트
if (skuDetailsList == null) {
Log.d(TAG,"결제 상품 리스트에 없음 ");
return;
}
//상품사이즈 체크
Log.d(TAG, "결제 상품 리스트 크기 : " + skuDetailsList.size());
try {
for (SkuDetails skuDetails : skuDetailsList) {
String title = skuDetails.getTitle();
String sku = skuDetails.getSku();
String price = skuDetails.getPrice();
}
} catch (Exception e) {
Log.d(TAG, "itemerror" + e.toString());
}
skuDetails_list = skuDetailsList;
}
});
}
//인자중 itemid 는 구매 품목 id를 문자열로, Activity 는 현재 액티비티를 넣어주면 됩니다.
//이 Activity인자는 구매를 성공했는지 아닌지를 값을 반환해 주기 위해 있는 인자이므로 그게
//신경을 안써도 됩니다.
public void purchase(String itemid, Activity activity) {
SkuDetails skuDetails = null;
if(null != skuDetails_list){
for(int i=0; i<skuDetails_list.size(); i++){
SkuDetails skuinfo = skuDetails_list.get(i);
if(skuinfo.getSku().equals(itemid)){//해당 상품을 상품목록에 있는지 비교하고 있으면 다음으로 넘어갑니다.
skuDetails = skuinfo;
break;
}
}
Log.d(TAG,"biilingcount4");
// Retrieve a value for "skuDetails" by calling querySkuDetailsAsync().
BillingFlowParams flowParams = BillingFlowParams.newBuilder()
.setSkuDetails(skuDetails) //구매 flow에 해당 목록id가 저장된 변수를 넣습니다.
.build();
Log.d(TAG,"purchase state : "+billingClient.launchBillingFlow(activity,flowParams).getResponseCode());
// 아까 말했다시피 단지 성공여부를 알려주는 값입니다. Activity는 그냥 현재, 결제하고 있는 Activity를 입력하시면 됩니다.
}
}
//이부분은 구매를 하는 함수입니다.
// 버튼클릭하면 구매를 원할시 버튼이벤트에 이 함수를 넣어주면 됩니다.
//위에 선언된 purchase를 이 함수의 인자에 넣어주고 함수를 실행하면 됩니다.
그럼 구매, 토큰값 구매자에게 적용 등등 작업을하여 구매 완료를 이끌어 냅니다.
public void handlePurchase(Purchase purchase) {
Log.d(TAG,"biilingcount5");
// Purchase retrieved from BillingClient#queryPurchasesAsync or your PurchasesUpdatedListener.
ConsumeParams consumeParams =
ConsumeParams.newBuilder()
.setPurchaseToken(purchase.getPurchaseToken())
.build();
//구매가 되면 위에서 처럼 ComsumeResponesListner을 가진 변수로 결제 성공 후 이벤트를 등록합니다.
//결제상태를 계속 체크하면서, 장애, 정보등을 전당해 줍니다.
ConsumeResponseListener listener = new ConsumeResponseListener() {
@Override
public void onConsumeResponse(BillingResult billingResult, String purchaseToken) {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
// Handle the success of the consume operation.
}
}
};
billingClient.consumeAsync(consumeParams, listener);
}
}
좀 길지만 한 3번정도 저의 주석을 읽으면서 순서랑 논리구초 파악하면 아주 간단하다는것을 느끼실 겁니다.
안드로이드 개발자 가이드는 함수들만 알려주고 어떤기능이 있는지 어디에 넣어줘야 하는지에 대한 정보가 없어서
화나서 글 포스팅 합니다 ㅎㅎ..
그리고 코드가 완벽한데 오류가 뜨는사람이 있습니다.
그 오류 내용이 만약 => W/BillingClient: In-app billing API version 3 is not supported on this device.
라면, 또한 환경이 에뮬레이터라면, 아주 간단하게 해결 가능합니다.
에뮬레이터는 맨처음 구글스토어가 활성화가 안돼있습니다.
구글스토어 앱 들어가서 로그인하고 활성화(그냥로그인하거나 걍 뭐 버튼 누르다보면 활성화돼요 ㅎㅎ) 하고
에뮬 껏다키면 바로 됩니다. ~~
그럼 다음엔 더 좋은 정보로 글쓸게요~ 안녕!
'IT기술(코딩)' 카테고리의 다른 글
안드로이드 스튜디오 네이버맵 크기조정 방법. naver map (0) | 2021.08.26 |
---|---|
안드로이드 스튜디오, 자바 Thread 완료될때까지 대기 (로딩중) Runnable 을 Thread로 바꾸어 사용하세요~ (0) | 2021.08.24 |
안드로이드 스튜디오 슬라이드 뷰 만들기 [java][자바] 스와이프, 페이지뷰 (0) | 2021.07.27 |
안드로이드 스튜디오 앱 전체 글로벌 변수 application 클래스 사용방법 액티비티 간 데이터 전송 (0) | 2021.07.21 |
안드로이드 스튜디오 네이버 맵 다중 마커(마커 여러개)JAVA버전 (0) | 2021.07.19 |