-
Google Play InApp v5 구글플레이 인앱 결제 버전5Java 2022. 10. 25. 16:40728x90반응형
22년 11월 1일부터, 구글플레이 모든 앱의 인앱결제 라이브러리 버전3을 사용이 완전 종료된다. 따라서, v4나 v5로 마이그레이션을 필수적으로 해야 하는데,
망할 구글 샘플 소스 https://developer.android.com/google/play/billing/integrate?hl=ko#java 는 ImmutableList 라는 클래스 덕택에 컴파일 되지 않는다.
삽질끝에 ArrayList로 변경하여 구현하는데 성공
1. build.graddle(:app)에 라이브러리 추가
def billingVersion = "5.0.0" implementation "com.android.billingclient:billing:$billingVersion"
2. graddle 변경후에는 반드시 "Sync Project with Graddle Files"를 해준다.
3. 빌링 모듈 클래스를 만들자. PurchaseUpdateListener 와 ConsumeResponseListener 를 구현해야 한다.
public class BillingModule implements PurchasesUpdatedListener, ConsumeResponseListener {
4. 이 모듈에서 발생하는 이벤트를 받을 콜백 인터페이스를 선언해주자
public interface BillingModuleCallback { void onBillingModulesIsReady(List<ProductDetails> detailsList); void onSuccess(Purchase purchase); void onFailure(int errorCode); void onInit(int result); }
5. 생성자 선언. 이 모듈의 엄마(?)가 될 액티비티를 전달해주어야 한다.
BillingClient billingClient = null; Activity callActivity = null; BillingModuleCallback callBack = null; public BillingModule(Activity activity, BillingModuleCallback callback) { billingClient = BillingClient.newBuilder(activity) .setListener(this) .enablePendingPurchases() .build(); billingClient.startConnection(new BillingClientStateListener() { @Override public void onBillingSetupFinished(BillingResult billingResult) { if (billingResult.getResponseCode() == BillingResponseCode.OK) { // The BillingClient is ready. You can query purchases here. callBack.onInit(0); } } @Override public void onBillingServiceDisconnected() { // Try to restart the connection on the next request to // Google Play by calling the startConnection() method. } }); callActivity = activity; callBack = callback; } public void dispose() { billingClient.endConnection(); }
빌링 셋업이 성공적으로 완료되면 엄마의 onInit();를 호출하게 만들어줬다. 부모는 onInit() 를 받으면, SKU 리스트를 달라고 요청한다.
... BillingModule bm = null; ... @Override public void onCreate (Bundle savedInstanceState) { ... bm = new BillingModule(this, this); ... } final String SKU_STAR10 = "star10"; final String SKU_STAR20 = "star20"; final String SKU_STAR100 = "star100"; @Override public void onInit(int result) { // TODO Auto-generated method stub if(result == BillingClient.BillingResponseCode.OK){ ArrayList<String> listSku = new ArrayList <String> (); listSku.add(SKU_STAR10); listSku.add(SKU_STAR20); listSku.add(SKU_STAR100); bm.asyncGetReadyToBuy(listSku); } else { Log.d("iap5", "onInit error : " + result); } }
MainActivity의 onInit 구현
6. getReadyToBuy 의 구현
public void asyncGetReadyToBuy(ArrayList<String> listSku) { ArrayList <QueryProductDetailsParams.Product> listProduct = new ArrayList<>(); for(String sku : listSku) { Log.d("iap5", "asyncGetReadyToBuy sku : " + sku); listProduct.add(QueryProductDetailsParams.Product.newBuilder().setProductId(sku).setProductType(BillingClient.ProductType.INAPP).build()); } QueryProductDetailsParams params = QueryProductDetailsParams.newBuilder() .setProductList(listProduct) .build(); billingClient.queryProductDetailsAsync( params, new ProductDetailsResponseListener() { public void onProductDetailsResponse(BillingResult billingResult, List<ProductDetails> productDetailsList) { // Process the result Log.d("iap5", "onProductDetailsResponse : " + productDetailsList.size()); callBack.onBillingModulesIsReady(productDetailsList); } } ); }
지정된 SKU들의 Detail을 달라고 요청하고, 요청이 도착하면 엄마를 콜백한다.ArrayMap<String, ProductDetails> skus = new ArrayMap<String, ProductDetails> (); @Override public void onBillingModulesIsReady(List<ProductDetails> listSku) { // TODO Auto-generated method stub if(listSku == null) { Log.d("iap5", "onBillingModulesIsReady sku is null"); } else { for(ProductDetails sku : listSku){ Log.d("iap5", "onBillingModulesIsReady sku : " + sku.toString()); skus.put(sku.getProductId(), sku); } } }
MainActivity의 onBillingModilesIsReady 구현 : 준비가된 SKU들을 어레이맵에 담아둔다.
7. 여기까지 결제준비는 완료되었다! 이제 사용자가 구매를 눌렀을때 처리를 시작해보자. 사용자가 어떤 아이템 구매버튼을 누르면, 이 아이템의 ProductDetail (예전 버전에서는 SKU Detail)을 알아내서 구글플레이 서버로 전송해야 한다.
public boolean buyStar(String productId) { if(skus.isEmpty()){ showToast("Not available (NO SKUS)"); return false; } ProductDetails sku = skus.get(productId); if(sku == null){ showToast(String.format("Not available (NO SKUS : %s)", productId)); return false; } bm.asyncLaunchFlow(sku); return true; }
8. 빌링 플로우 시작
public int asyncLaunchFlow(ProductDetails productDetails){ ArrayList<BillingFlowParams.ProductDetailsParams> productDetailsParamsList = new ArrayList<>(); productDetailsParamsList.add(BillingFlowParams.ProductDetailsParams.newBuilder() .setProductDetails(productDetails) .build() ); BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder() .setProductDetailsParamsList(productDetailsParamsList) .build(); Log.d("iap5", "asyncLaunchFlow : " + productDetails.getProductId()); return billingClient.launchBillingFlow(callActivity, billingFlowParams).getResponseCode(); }
9. 구매를 성공(혹은 실패)하면 onPurchaseUpdated 가 호출된다.
@Override public void onPurchasesUpdated(BillingResult billingResult, List<Purchase> purchases) { // TODO Auto-generated method stub Log.d("iap5", "onPurchasesUpdated : " + billingResult.getResponseCode()); if (billingResult.getResponseCode() == BillingResponseCode.OK && purchases != null) { for (Purchase purchase : purchases) { handlePurchase(purchase); } } else if (billingResult.getResponseCode() == BillingResponseCode.USER_CANCELED) { // Handle an error caused by a user cancelling the purchase flow. } else { // Handle any other error codes. } }
구매성공하면, 구매가 아니라 구매들(!)이 리스트로 넘어온다. 이제 이것들을 잘 처리하면 된다.
10. 구매 물건에 대한 처리 = Consumeprivate Purchase curPurchase = null; void handlePurchase(Purchase purchase) { curPurchase = purchase; ConsumeParams consumeParams = ConsumeParams.newBuilder() .setPurchaseToken(purchase.getPurchaseToken()) .build(); Log.d("iap5", "handlePurchase : " + purchase.getProducts().get(0)); billingClient.consumeAsync(consumeParams, this); }
11. Consume 에 대한 처리 : 최종적으로 부모의 onSuccess()가 호출된다.
@Override public void onConsumeResponse(BillingResult billingResult, String token) { // TODO Auto-generated method stub if (billingResult.getResponseCode() == BillingResponseCode.OK) { // Handle the success of the consume operation. Log.d("iap5", "onConsumeResponse : " + billingResult.getResponseCode()); callBack.onSuccess(curPurchase); } }
12. 결제 프로세스가 중단되는경우 (결제는 완료했는데, Consume 이 되지 않은 경우) 에는 다시 확인하고 처리해야한다.public void resume(String ptype){ Log.d("iap5", "resume : " + ptype); if (billingClient.isReady()) { billingClient.queryPurchasesAsync( QueryPurchasesParams.newBuilder().setProductType(ptype).build(), new PurchasesResponseListener() { public void onQueryPurchasesResponse(BillingResult billingResult, List<Purchase> purchases) { Log.d("iap5", "onResume.onQueryPurchasesResponse : " + billingResult.getResponseCode()); if (billingResult.getResponseCode() == BillingResponseCode.OK && purchases != null) { for (Purchase purchase : purchases) { handlePurchase(purchase); } } } } ); }
@Override protected void onResume() { // TODO Auto-generated method stub super.onResume(); bm.resume(BillingClient.ProductType.INAPP); }
MainActivity의 onResume 구현