구글의 인앱결제 정책에 따라 현재 서비스 하고 있는 어플에 인앱 결제 기능을 추가해야한다.
결제 정책은 아래 링크에서 확인 할 수 있다.
하.. 발등에 또ㅇ.. 아니 불이 떨어졌다. 😇
얼른 인앱 결제를 구현해보자
(아래 내용은 Google Play 결제 시스템 문서를 참고함)
dependencies {
implementation 'com.android.billingclient:billing:4.1.0'
}
val billingClient = BillingClient.newBuilder(this)
.setListener(PurchasesUpdatedListener { billingResult, purchases ->
//모든 구매 관련 업데이트를 수신한다.
})
.enablePendingPurchases()
.build()
billingClient.startConnection(object : BillingClientStateListener {
override fun onBillingServiceDisconnected() {
// 연결 실패 시 재시도 로직을 구현.
}
override fun onBillingSetupFinished(billingResult: BillingResult) {
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
// 준비 완료가 되면 상품 쿼리를 처리 할 수 있다!
}
}
})
private fun querySkuDetails() {
val skuList = ArrayList<String>()
skuList.add("item_id_1")
skuList.add("item_id_2")
skuList.add("item_id_3")
val params = SkuDetailsParams.newBuilder().apply {
setSkusList(skuList)
setType(BillingClient.SkuType.INAPP) //정기 구독일 경우 BillingClient.SkuType.SUBS
}.build()
billingClient.querySkuDetailsAsync(params) { billingResult, skuDetailsList ->
// 완료되면 SkuDetails(상품 상세 정보)를 List 형태로 반환한다.
}
}
val flowParams = BillingFlowParams.newBuilder()
.setSkuDetails(skuDetails)
.build()
val billingResult = billingClient.launchBillingFlow(
this,
flowParams
)
//launchBillingFlow()는 BillingResponseCode를 반환한다.
if( billingResult.responseCode != BillingClient.BillingResponseCode.OK ) {
//오류가 발생 할 경우 여기서 처리
}
호출에 성공하면 다음과 같은 구매 화면이 표시된다. (테스트 결제는 다음 챕터에서 설명)
구매 결과는 BillingClient 초기화 시 추가했던 PurchasesUpdatedListener 리스너에 전송된다.
val billingClient = BillingClient.newBuilder(this)
.setListener(PurchasesUpdatedListener { billingResult, purchases ->
//모든 구매 관련 업데이트를 수신한다.
purchasesUpdated(billingResult, purchases)
})
.enablePendingPurchases()
.build()
private fun purchasesUpdated(billingResult: BillingResult, purchases: List<Purchase>?) {
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK && purchases != null) {
for (purchase in purchases) {
//구매 성공 시 처리
}
} else if (billingResult.responseCode == BillingClient.BillingResponseCode.USER_CANCELED) {
// 사용자가 구매를 취소했을 경우 처리
} else {
// 이외의 오류 처리
}
}
private fun handlePurchase(purchase: Purchase) {
// 소비 처리 이전에 구매 검증 필요
// 소비 처리
val consumeParams =
ConsumeParams.newBuilder()
.setPurchaseToken(purchase.purchaseToken)
.build()
billingClient.consumeAsync(consumeParams) { billingResult, str ->
//소비 처리에 대한 결과 처리
}
}
billingClient.queryPurchasesAsync(BillingClient.SkuType.INAPP) { billingResult, purchaseList ->
if( billingResult.responseCode == BillingClient.BillingResponseCode.OK ) {
purchaseList.forEach {
handlePurchase(it)
}
}
구매(결제) 검증은 민감한 데이터 로직이므로 서버 사이드에서 처리해야한다.
사용자가 구매 완료시 반환 받는 purchaseToken을 서버로 전송하여 구매(결제) 검증을 해야하는데,
Google Developer API 의 Purchases.products.get(일회성 구매 상품 일 경우) 또는 Purchases.subscriptions:get(정기 구독 상품 일 경우) 를 사용하여 구매(결제) 검증을 진행한다.
더 자세한 내용은 아래 링크를 참고하자.
아래에서 짧게 설명할 실시간 개발자 알림은 일회성 구매 상품에 대한 환불을 처리하기에는 부적절하므로 일회성 구매 상품의 환불은 Google Developer API 의 Voided Purchases 를 사용하여 처리해야한다. 해당 API를 사용해 무효화된 구매에 대해 확인하고, 무효화된 구매와 관련된 제품(또는 콘텐츠)에 엑세스 하지 못하도록 하는 시스템을 구현해야한다.
더 자세한 내용은 아래 링크를 참고하자.
실시간 개발자 알림(RTDN) 은 앱 내에서 사용자의 사용 권한이 변경될 때마다 Google의 알림을 수신하는 메커니즘으로 Google Cloud의 Pub/Sub을 활용한다.
다만 해당 알림으로는 정기 구독에 대한 알림과 일회성 구매 중에서도 지연된 결제건에 대한 알림만 받을 수 있어 이번 내용에서는 다루지 않았다. 더 자세한 내용은 아래 링크를 참고하자.