이 주제에서는 Android 인터페이스 정의 언어(AIDL)를 사용하는 결제 통합으로부터 이전하는 방법을 설명합니다. AIDL을 사용하여 Google Play 결제 시스템에 액세스하는 기능은 지원 중단되었으며, 앞으로 모든 통합에서 Google Play 결제 라이브러리를 사용해야 합니다.
이전 단계
Google Play 결제 라이브러리 가져오기
먼저 Google Play 결제 라이브러리에 종속 항목을 추가합니다. Gradle을 사용한다면 앱의 build.gradle
파일에 다음을 추가할 수 있습니다.
Groovy
dependencies { def billing_version = "6.0.1" implementation "com.android.billingclient:billing:$billing_version" }
Kotlin
dependencies { val billing_version = "6.0.1" implementation("com.android.billingclient:billing:$billing_version") }
이전 참조 코드에서 복사했을 수도 있는 IabHelper
와 같은 '글루' 코드는 삭제해도 됩니다. IabHelper
에서 제공하는 기능이 이제 Google Play 결제 라이브러리에서 지원됩니다.
com.android.vending.BILLING
권한 삭제
Google Play 결제 라이브러리는 매니페스트에 com.android.vending.BILLING
권한을 삽입합니다. 이제 앱 매니페스트에 이 권한을 명시적으로 추가할 필요가 없습니다.
Google Play 결제에 연결
Google Play 결제 라이브러리 메서드 BillingClient
가 연결 관리를 처리합니다. 이전하려면 앱을 다음과 같이 변경합니다.
BillingClient
인스턴스를 생성합니다.BillingClientStateListener
를 구현하여 서비스 상태에 관한 콜백을 수신합니다.BillingClient
인스턴스의startConnection()
을 호출합니다.- 인앱 구매와 관련된
onActivityResult()
코드를 삭제하고PurchasesUpdatedListener
로 이동합니다.
다음 예는 이러한 변경사항을 적용하기 전과 후의 앱 모습을 보여줍니다.
전
mServiceConn = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
...
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
...
}
};
Intent serviceIntent = new Intent("com.android.vending.billing.InAppBillingService.BIND");
serviceIntent.setPackage("com.android.vending");
List<ResolveInfo> intentServices = mContext.getPackageManager()
.queryIntentServices(serviceIntent, 0);
if (intentServices != null && !intentServices.isEmpty()) {
mContext.bindService(serviceIntent, mServiceConn, Context.BIND_AUTO_CREATE);
} else {
// Handle errors.
...
}
...
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
IabResult result;
if (requestCode != mRequestCode || data == null) {
// Handle errors.
...
}
int responseCode = getResponseCodeFromIntent(data);
String purchaseData = data.getStringExtra(RESPONSE_INAPP_PURCHASE_DATA);
String dataSignature = data.getStringExtra(RESPONSE_INAPP_SIGNATURE);
if (resultCode != Activity.RESULT_OK || responseCode != BILLING_RESPONSE_RESULT_OK) {
// Handle errors.
...
}
// Process successful purchase.
...
return true;
}
후
Kotlin
class MyBillingImpl(private var billingClient: BillingClient) : PurchasesUpdatedListener { init { billingClient = BillingClient.newBuilder(activity).setListener(this).build() billingClient.startConnection(object : BillingClientStateListener { override fun onBillingSetupFinished(billingResult: BillingResult?) { // Logic from ServiceConnection.onServiceConnected should be moved here. } override fun onBillingServiceDisconnected() { // Logic from ServiceConnection.onServiceDisconnected should be moved here. } }) } override fun onPurchasesUpdated( billingResult: BillingResult?, purchases: MutableList<Purchase>? ) { // Logic from onActivityResult should be moved here. } }
자바
public class MyBillingImpl implements PurchasesUpdatedListener { private BillingClient billingClient; ... public void initialize() { billingClient = BillingClient.newBuilder(activity).setListener(this).build(); billingClient.startConnection(new BillingClientStateListener() { @Override public void onBillingSetupFinished(BillingResult billingResult) { // Logic from ServiceConnection.onServiceConnected should be moved here. } @Override public void onBillingServiceDisconnected() { // Logic from ServiceConnection.onServiceDisconnected should be moved here. } }); } @Override public void onPurchasesUpdated( @BillingResponse int responseCode, @Nullable List<Purchase> purchases) { // Logic from onActivityResult should be moved here. } }
구매하기
구매 대화상자를 시작하려면 다음을 실행합니다.
- SKU 세부정보
Bundle
을SkuDetailsParams
로 변환합니다. mService.getSkuDetails()
호출을 대신BillingClient.querySkuDetailsAsync()
를 사용하도록 전환합니다.- 구매 인텐트
Bundle
을BillingFlowParams
객체로 변환합니다. mService.getBuyIntent()
호출을 대신BillingClient.launchBillingFlow()
를 사용하도록 전환합니다.onActivityResult()
에서 인앱 구매 관련 코드를 삭제하고 이 코드를PurchasesUpdatedListener
로 이동합니다.
다음 예는 이러한 변경사항을 적용하기 전과 후의 앱 모습을 보여줍니다.
전
// Query Skus
String skuToSell = "premium_upgrade";
ArrayList<String> skus = new Arraylist<>();
skus.add(skuToSell);
Bundle querySkus = new Bundle();
querySkus.putStringArrayList(GET_SKU_DETAILS_ITEM_LIST, skus);
Bundle skuDetails = mService.getSkuDetails(3,
mContext.getPackageName(),
itemType,
querySkus);
if (!skuDetails.containsKey(RESPONSE_GET_SKU_DETAILS_LIST)) {
// Handle errors.
...
}
// Launch Buy Flow
Bundle buyIntentBundle = mService.getBuyIntent(3,
mContext.getPackageName(),
skuToSell,
"Inapp",
"");
int response = getResponseCodeFromBundle(buyIntentBundle);
if (response != BILLING_RESPONSE_RESULT_OK) {
// Handle errors.
...
}
PendingIntent pendingIntent = buyIntentBundle.getParcelable(RESPONSE_BUY_INTENT);
act.startIntentSenderForResult(pendingIntent.getIntentSender(),
requestCode,
new Intent(),
Integer.valueOf(0),
Integer.valueOf(0),
Integer.valueOf(0));
// Purchase is handled in onActivityResult illustrated in the previous section.
후
Kotlin
val skuToSell = "premium_upgrade" val skuList = mutableListOf<String>() skuList.add(skuToSell) val params = SkuDetailsParams.newBuilder() params.setSkusList(skuList).setType(BillingClient.SkuType.INAPP) billingClient.querySkuDetailsAsync(params.build(), object : SkuDetailsResponseListener { override fun onSkuDetailsResponse( billingResult: BillingResult?, skuDetailsList: MutableList<SkuDetails>?) { // Process the result. } }) // SkuDetails object obtained above. val skuDetails = ... val purchaseParams = BillingFlowParams.newBuilder() .setSkuDetails(skuDetails) .build() billingClient.launchBillingFlow(activity, purchaseParams) // Purchase is handled in onPurchasesUpdated illustrated in the previous section
자바
String skuToSell = "premium_upgrade"; List<String> skuList = new ArrayList<> (); skuList.add(skuToSell); SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder(); params.setSkusList(skuList).setType(SkuType.INAPP); billingClient.querySkuDetailsAsync(params.build(), new SkuDetailsResponseListener() { @Override public void onSkuDetailsResponse(BillingResult billingResult, List<SkuDetails> skuDetailsList) { // Process the result. ... } }); // SkuDetails object obtained above. SkuDetails skuDetails = ...; BillingFlowParams purchaseParams = BillingFlowParams.newBuilder() .setSkuDetails(skuDetails) .build(); mBillingClient.launchBillingFlow(mActivity, purchaseParams); // Purchase is handled in onPurchasesUpdated illustrated in the previous section.
구매 항목 소비
Google Play 결제 라이브러리를 사용하여 구매 항목을 소비하는 방법은 다음과 같습니다.
consumePurchase()
를 호출하는 대신BillingClient.consumeAsync()
를 호출합니다.ConsumeResponseListener
를 구현합니다.
다음 예는 이러한 변경사항을 적용하기 전과 후의 앱 모습을 보여줍니다.
전
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
int responseCode = data.getIntExtra(RESPONSE_CODE);
JSONObject purchaseData =
new JSONObject(data.getStringExtra("INAPP_PURCHASE_DATA"));
String token = purchaseData.get("purchaseToken");
...
// Consume purchase
int response = mService.consumePurchase(3, mContext.getPackageName(), token);
if (response != BILLING_RESPONSE_RESULT_OK) {
// Handle errors.
...
}
// Handle successful consumption.
}
후
Kotlin
class MyBillingImpl(private val billingClient: BillingClient) : ... , ConsumeResponseListener { fun consumePurchase(purchaseToken: String) { val consumeParams = ConsumeParams .newBuilder() .setPurchaseToken(purchaseToken) .build() } override fun onConsumeResponse( billingResult: BillingResult?, purchaseToken: String?) { // Handle consumption } }
자바
public class MyBillingImpl implements ..., ConsumeResponseListener { private BillingClient billingClient; ... public void consumePurchase(String purchaseToken) { ConsumeParams consumeParams = ConsumeParams.newBuilder() .setPurchaseToken(purchaseToken) .build(); } @Override void onConsumeResponse(BillingResult billingResult, String purchaseToken) { // Handle consumption. ... } }
구매 항목 확인
Google Play 결제 라이브러리 버전 2.0부터는 앱에서 모든 구매 항목을 소비하거나 확인해야 합니다.
3일 이내에 구매 항목을 소비하거나 확인하지 않으면 Google에서 자동으로 구매를 취소하고 사용자에게 환불합니다. 자세한 내용은 구매 항목 확인을 참고하세요.
앱 외부 구매 인식
앱 외부 구매 처리를 Google Play 결제 라이브러리로 이전하려면 다음 안내를 따르세요.
- 앱의
onResume()
콜백에서 앱이BillingClient.queryPurchasesAsync()
를 호출하는지 확인합니다. com.android.vending.billing.PURCHASES_UPDATED
의 broadcast receiver를 삭제하고 이에 대응하는 콜백 코드를PurchasesUpdatedListener
로 이동합니다.
이전에는 Google Play 결제를 AIDL과 통합할 때 앱 외부에서 발생한 구매를 처리하기 위해 앱이 리스너를 등록하여 com.android.vending.billing.PURCHASES_UPDATED
인텐트를 수신해야 했습니다.
Google Play 결제 라이브러리를 사용하면 앱은 항상 앱이 실행되지 않는 동안 발생한 모든 구매가 인식된 것을 확인하는 첫 단계로 앱의 onResume()
콜백에서 queryPurchasesAsync()
를 호출해야 합니다. 앱이 실행되는 동안 Google Play 결제 라이브러리는 앱 외부 구매를 자동으로 수신 대기하여 PurchasesUpdatedListener
를 통해 알려줍니다.
대기 중인 거래 처리
Google Play 결제 라이브러리 버전 2.0부터 앱은 자격을 부여하기 전에 구매 후 추가 작업이 필요한 대기 중인 거래를 처리해야 합니다. 예를 들어, 사용자가 오프라인 상점에서 현금으로 인앱 상품을 구매하기로 선택할 수도 있습니다. 이는 거래가 앱 외부에서 완료된다는 것입니다. 이 시나리오에서는 사용자가 거래를 완료한 후에만 자격을 부여해야 합니다.
자세한 내용은 대기 중인 거래 지원을 참고하세요.
개발자 페이로드
개발자 페이로드는 과거에 사기를 예방하고 구매에 기여한 사용자를 정확하게 분석하는 등 다양한 목적으로 사용되었습니다. 이제 Google Play 결제 라이브러리에서 이러한 사용 사례를 지원하므로 Google Play 결제 라이브러리의 버전 2.2부터는 개발자 페이로드 지원이 중단됩니다. 자세한 내용은 개발자 페이로드를 참고하세요.
자세한 오류 메시지
Google Play 결제 라이브러리 버전 2.0부터 모든 오류에는 상응하는 디버깅 관련 메시지가 있습니다. 이러한 메시지는 BillingResult.getDebugMessage()
를 호출하여 얻을 수 있습니다.