안드로이드 인앱 결제에 관하여 노베이스부터 구현하기 까지 과정과
코드를 정리할겸 포스팅을 시작해보겠다.
본론부터 정리하자면 우리는 구글 실시간 개발자 알림을 구성하지 못하였기 때문에 최대한 프론트(앱)단에서 할 수 있는 걸 처리해보았다.
Purchases.sharedInstance.getOfferingsWith { offerings: Offerings ->
Log.i("offerings.current!!.availablePackages", "${offerings.all.values}")
OfferList = offerings.all
}
우리는 revenueCat 라이브러리를 이용했다. 구글에서 제공해주는 것보다 영수증 처리등 다양한 것을 제공해주기에 쓴 면이 크고 당연히 유료이다.
위의 코드는 revenueCat에 구글콘솔의 인앱결제 상품들을 연결했다는 가정하에 reenueCat에 연결한 인앱 결제 상품들을 가져오는 코드다.
Purchases.sharedInstance.purchasePackageWith(this@SubscribeActivity,
selectedSubscription!!.offering!!,
onError = { error, userCancelled ->
if (!userCancelled) {
Log.i(TAG, error.message)
}
},
onSuccess = { store, customer ->
Log.i(TAG, "")
})
위의 코드는 내가 선택한 상품에 대해 구매하는 로직이다.
비동기로 onError,onSucess가 내려오는데 이때 까지만 해도
인앱결제가 이렇게 복잡하고 어려울 줄 몰랐다.(지금생각해보면 당연한거지만..)
단순히 구매만 시켜서는 안되었다.
왜냐? 인앱 결제에는 여러가지 상황이 있다. 크게 4가지로 나누어보았다
구매했던 상품과 같은 구글스토어 계정이지만 다른 uid로 접속한 경우 -> 이미 다른 계정으로 이용권을 구독중입니다. 구독 중인 아이디를 확인해주세요
구매했던 상품과 다른 구글스토어 계정이지만 같은 uid로 접속한 경우 ->
이용권 변경이 불가능합니다. 기존에 구매했던 Google 계정이 맞는지 확인해주세요.
같은 스토어,uid 지만 다른 상품을 선택한 경우 -> upgrage/downgrade 로직 진행
3번의 경우이지만 같은 상품을을 선택한 경우 -> 이미 선택한 상품입니다.
이렇게 존재하였다.
자 그러면 어떻게 아느냐
위의 4가지 경우를 알기 위해선 최초 구매시 transactionId를 저장했다.
그리고 구매 요청이 있을대마다 플레이스토어의 구매이력을 조회하여서 같은 transactionId가 있는지 없는지를 검사하였다.
manager = BillingManager(this, object : BillingCallback {
override fun onBillingConnected() {
Log.i(TAG,"")
}
override fun onSuccess(purchase: Purchase) {
Log.i(TAG, "")
Purchases.sharedInstance.getCustomerInfo(object : ReceiveCustomerInfoCallback {
override fun onError(error: PurchasesError) {
Log.i(TAG, error.message)
}
override fun onReceived(customerInfo: CustomerInfo) {
//처음 구독이지만 구글 결제프로필 구매 내역 있음 -> "이미 다른 프로 계정으로 이용권을 구독중입니다. 구독 중인 아이디를 확인해주세요"
customerInfo.entitlements.active.forEach {
customerSucribeID = it.key
}
if (customerInfo.entitlements.active.isEmpty()) {
//구매 내역 있기 때문에 주문 번호까지 같은지 다른지 체크해야함.. 같다면 앱ID만 다른 경우임
DialogUtil.show(
this@SubscribeActivity,
"이미 다른 프로 계정으로 이용권을 구독중입니다. 구독 중인 아이디를 확인해주세요",
getStringValue(R.string.btn_confirm)
) {}
}
//이미 구독중
else if (customerInfo.entitlements.active[selectedSubscription!!.offering!!.offering] != null) {
DialogUtil.show(
this@SubscribeActivity,
"이미 구독 중인 상품입니다.",
getStringValue(R.string.btn_confirm)
) {}
}
//upgrade , downgrade -> 구독 정보가 있는 거니까 결제 프로필 상으로도 같다는 조건을 걸자 -> upgrade/downgrade 시키자.
else {
if (purchase?.orderId == AppHelper.dataStorage.transactionId) {
//upgrade 시키면됨
Purchases.sharedInstance.purchasePackageWith(this@SubscribeActivity,
selectedSubscription!!.offering!!,
UpgradeInfo(
customerSucribeID,
BillingFlowParams.ProrationMode.IMMEDIATE_WITH_TIME_PRORATION
),
onError = { error, userCancelled ->
if (!userCancelled) {
Log.i(TAG, error.message)
}
},
onSuccess = { store, customer ->
Log.i(TAG, "")
})
}
//앱 ID는 같지만 결제한 정보가 다르다..
else {
DialogUtil.show(
this@SubscribeActivity,
"이용권 변경이 불가능합니다. 기존에 구매했던 Google 계정이 맞는지 확인해주세요.",
getStringValue(R.string.btn_confirm)
)
{}
}
}
}
})
}
override fun onFailure(responseCode: Int) {
//구매한 이력이 아무것도 없다..
override fun onUpdated(purchase: Purchase) {
//TODO
})
manager는 구글 결제 라이브러리로써 구글 결제에 관한 처리를 할 수 있는 부분이다. manager가 success를 한다면 이 스토어계정으로 무언가 결제한 이력이 있다는 뜻이다. 그렇다면 revenueCat에게도 물어봐서 transactionID가 동일한지를 체크해보았다. onReceived 콜백 아래에
3가지 분기가 되어있는데 설명해보자면
스토어에는 구매 이력이 있지만 revenueCat에는 비어있다? -> 해당 앱 id로는 결제한적이 없는 것이다.
revenueCat에 구독정보와 현재 선택한 상품 정보가 일치기 때문에 같은 상품을 선택한 경우다.
1,2번도 아닌 경우는 스토어 결제 이력이 있고 동일 상품도 아니기에 upgrade/downgrade를 요청했다고 보았다. 그치만 확신할 수 없기에 transacionId를 검사하는 로직을 추가했다.
else {
if (purchase?.orderId == AppHelper.dataStorage.transactionId) {
//upgrade 시키면됨
Purchases.sharedInstance.purchasePackageWith(this@SubscribeActivity,
selectedSubscription!!.offering!!,
UpgradeInfo(
customerSucribeID,
BillingFlowParams.ProrationMode.IMMEDIATE_WITH_TIME_PRORATION
),
onError = { error, userCancelled ->
if (!userCancelled) {
Log.i(TAG, error.message)
}
},
onSuccess = { store, customer ->
Log.i(TAG, "")
})
}
위와 같은 식으로 처리했다.
자 그럼 이제 나머지 경우, 구글 결제 라이브러리에서 실패한 경우에 대한 처리이다.
override fun onFailure(responseCode: Int) {
//구매한 이력이 아무것도 없다..
if (responseCode == -1000) {
Purchases.sharedInstance.getCustomerInfo(object : ReceiveCustomerInfoCallback {
override fun onError(error: PurchasesError) {
Log.i(TAG, error.message)
}
override fun onReceived(customerInfo: CustomerInfo) {
//처음 구독이지만 구글 결제프로필 구매 내역도 없음-> 처음 구독 진행시켜
if (customerInfo.entitlements.active.isEmpty()) {
Purchases.sharedInstance.purchasePackageWith(this@SubscribeActivity,
selectedSubscription!!.offering!!,
onError = { error, userCancelled ->
if (!userCancelled) {
Log.i(TAG, error.message)
}
},
onSuccess = { store, customer ->
Log.i(TAG, "")
})
} //이미 구독중
else if (customerInfo.entitlements.active[selectedSubscription!!.offering!!.offering] != null) {
DialogUtil.show(
this@SubscribeActivity,
"이미 구독 중인 상품입니다.",
getStringValue(R.string.btn_confirm)
) {}
}
// 결제프로필은 없지만 앱 ID는 같음 다른 결제 프로필임 -> "이용권 변경이 불가능합니다. 기존에 구매했던 Google 계정이 맞는지 확인해주세요."
else {
DialogUtil.show(
this@SubscribeActivity,
"이용권 변경이 불가능합니다. 기존에 구매했던 Google 계정이 맞는지 확인해주세요.",
getStringValue(R.string.btn_confirm)
) {
}
}
}
})
}
}
마찬 가지로 3가지 부분으로 나누었는데.
구매한 이력이 없을 때 revenueCat에도 구매 이력이 없다면 -> 이건 아예 새롭게 구독 시작하는 경우이니 새로운 구매 로직을 진행하였다.
그치만 만약 같은 상품이 구독 중이라면? -> 이 경우는 스토어는 결제정보가 없지만 해당 앱id로는 결제정보가 있으므로 -> 이미 구독 중인 상품이라고 표시해주었다. (이 부분은 다른 앱들도 이렇게 처리했더라.. x빙 참고함)
자 그다음 else의 경우는 스토어에 결제 프로필은 없지만 앱ID 기준으로는 다른 결제 상품을 구독중인 것임 -> 그래서 "이용권 변경이 불가능합니다. 기존에 구매했던 Google 계정이 맞는지 확인해주세요." 라고 띄어주었다.
후우.. 여기까지가 내가 맨처음에 말한 4가지에 대한 로직 처리다!!
아직 update나 취소,환불 처리가 남았지만 나머지는 이어서 해보겠다.
(처음엔 2가지 버전으로 나눌 생각은 없었지만.. 쓰다보니 너무 길다!)