1. 권한 추가한 후 내부 테스트로 앱 등록
- 결제 권한을 가진 앱을 내부 테스트로 등록을 해줘야 정기 결제 상품을 등록할 수 있다.
- 그리고 아래 2가지는 꼭 추가해줘야 한다. 까먹고 manifest 파일에 해당 권한을 추가 안했더니 결제에서 오류가 계속 발생했다.
gradle에 추가
implementation 'com.android.billingclient:billing:7.1.1'
manifest에 permission 추가
<uses-permission android:name="com.android.vending.BILLING" />
2. 정기 결제 상품 등록
Google Play Console에 들어가서
홈 -> 앱 -> Play를 통한 수익 창출 -> 정기 결제 에 들어가서
제품 id랑 금액, 혜택 등 필요한 정보를 등록해둔다 !
그리고 활성화 를 시켜야 결제 테스트를 할 수 있다. 해둬야 한다!
3. 설정 -> 라이선스 테스트에 테스트할 Playstore 계정 추가
- 테스터로 등록이 안되면 테스트 결제를 할 수가 없다.
- 그리고 테스트 결제는 따로 결제수단은 추가안해도 된다.
4. 결제 처리를 하기 위한 코드 작성
https://developer.android.com/google/play/billing/integrate?hl=ko순서
구글에서 정리해놓은 코드이다.
이 코드가 최신이니 보고 확인하는 거 추천..
예전 티스토리 글 보고 했다가 함수가 바뀐 것들이 있어서 정기 결제 정보를 불러오지 못했었다.
1) Activity 코드에서는 BillingManaer 객체를 만들어서 부르면 된다.
billingManager = BillingManager(this@SubscribeInformActivity)
billingManager.startConnection()
2. 해당 activity 내에서 billingmanar 가 실행되면서 결제 bottom_sheet이 올라온다.
아직 오류 처리 로직은 구현을 안했다. 추가할 예정.
아래 코드는 테스트 bottom_sheet가 뜨고 purchaseToken까지 얻어온 시점이다.
이제 서버에 보내서 서버는 결제 검증을 하는 과정을 이제 추가로 해야한다.
설명해보자면, 순서는 이렇다.
- 결제 서비스 연결 (startConnection)
이 메서드는 Google Play의 결제 서비스에 연결을 시도하는 단계이다. 먼저 billingClient.startConnection()을 호출하여 두 가지 주요 이벤트를 처리한다:- onBillingServiceDisconnected()는 서비스가 끊어졌을 때의 처리를 담당하며, 네트워크 문제로 연결이 끊어졌을 때 재시도를 할 수 있다.
- onBillingSetupFinished()는 서비스 설정이 완료되었을 때 호출되며, 성공하면 querySubscriptionDetails()를 호출하여 구독 상품 정보를 가져온다.
- 구독 상품 정보 조회 (querySubscriptionDetails)
서비스 연결이 성공한 후 querySubscriptionDetails()를 호출하여 Google Play에서 구독 상품 정보를 조회한다.- QueryProductDetailsParams 객체를 사용하여 구독 상품의 ID와 구독 타입을 설정한 뒤, queryProductDetailsAsync() 메서드로 비동기적으로 상품 정보를 가져온다.
- 상품 정보가 성공적으로 조회되면, 첫 번째 상품 정보를 launchPurchaseFlow()로 전달하여 결제 플로우를 시작한다.
- 조회 실패 시 오류 로그를 출력한다.
- 구매 플로우 실행 (launchPurchaseFlow)
구독 상품 정보 조회가 성공하면, launchPurchaseFlow() 메서드를 호출하여 결제 플로우를 실행한다.- BillingFlowParams 객체를 생성하고, 구독 상품의 subscriptionOfferDetails에서 offerToken을 설정한 후 launchBillingFlow() 메서드를 통해 실제 결제를 시작한다.
- 구매 처리 (handlePurchase)
사용자가 결제를 완료하면 결제 결과에 따라 handlePurchase()가 호출된다.- 이 메서드는 구매 상태가 Purchase.PurchaseState.PURCHASED인지 확인하고, 실제로 결제가 완료된 상태에서만 구매 토큰(purchaseToken)을 서버로 전송하여 결제 검증을 진행한다.
- 만약 구매가 아직 확인되지 않았다면(purchase.isAcknowledged가 false), acknowledgePurchase()를 호출하여 구매 확인을 처리한다.
- 구매 확인 (acknowledgePurchase)
구매가 완료된 후 acknowledgePurchase() 메서드를 통해 Google Play에 구매가 정상적으로 처리되었음을 알린다.- 구매 확인을 통해 Google에 결제 완료 상태를 전달하며, 일정 기간 내에 구매 확인이 이루어지지 않으면 Google이 자동으로 환불 처리를 할 수 있다.
- 구매 확인이 성공하면 성공 로그가 출력되고, 실패 시 오류 로그가 출력된다.
코드
class BillingManager(private val activity: Activity) {
private lateinit var billingClient : BillingClient
init {
billingClient = BillingClient.newBuilder(activity)
.enablePendingPurchases()
.setListener { billingResult, purchases ->
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK && purchases != null) {
for (purchase in purchases) {
handlePurchase(purchase) // 구매 처리
}
} else {
// 결제 실패 처리
Timber.e("Purchase failed: ${billingResult.debugMessage}")
}
}
.build()
}
// 구매 정보 처리
private fun handlePurchase(purchase: Purchase) {
// 구매 토큰을 서버로 보내기 전에 구매 상태를 확인해야 함
if (purchase.purchaseState == Purchase.PurchaseState.PURCHASED) {
// 구매가 완료된 상태에서만 처리
val purchaseToken = purchase.purchaseToken
Timber.i("Purchase successful, token: $purchaseToken")
// 토큰을 서버로 전송하여 검증
sendTokenToServer(purchaseToken)
// 구매가 성공했음을 사용자에게 알림
if (!purchase.isAcknowledged) {
acknowledgePurchase(purchase)
}
}
}
// 서버로 토큰 전송
private fun sendTokenToServer(purchaseToken: String) {
// 서버로 토큰 전송을 위한 네트워크 요청
Timber.i("Sending token to server: $purchaseToken")
}
// 구매 확인 (Acknowledgement)
private fun acknowledgePurchase(purchase: Purchase) {
val acknowledgePurchaseParams = AcknowledgePurchaseParams.newBuilder()
.setPurchaseToken(purchase.purchaseToken)
.build()
billingClient.acknowledgePurchase(acknowledgePurchaseParams) { billingResult ->
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
Timber.i("Purchase acknowledged")
} else {
Timber.e("Failed to acknowledge purchase: ${billingResult.debugMessage}")
}
}
}
fun startConnection() {
billingClient.startConnection(object : BillingClientStateListener {
override fun onBillingServiceDisconnected() {
// Google Play 서비스 연결이 끊어진 경우 처리
Timber.e("checking 1")
}
override fun onBillingSetupFinished(billingResult: BillingResult) {
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
// 구독 상품 로드 또는 구매 가능 처리
Timber.e("checking 2")
querySubscriptionDetails()
}
}
})
}
fun querySubscriptionDetails() {
val queryProductDetailsParams = QueryProductDetailsParams.newBuilder()
.setProductList(
listOf(
QueryProductDetailsParams.Product.newBuilder()
.setProductId("프로덕션 아이디")
.setProductType(BillingClient.ProductType.SUBS)
.build()
)
).build()
billingClient.queryProductDetailsAsync(queryProductDetailsParams) { billingResult, productDetailsList ->
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK && !productDetailsList.isNullOrEmpty()) {
// SKU 세부 사항 로드 완료
val productDetails = productDetailsList[0] // 첫 번째 상품 정보 사용
// 구매 플로우 실행
Timber.e("checking 33 Error code: ${billingResult.responseCode}, message: ${productDetailsList}")
launchPurchaseFlow(productDetails)
} else {
// 오류 처리
Timber.e("checking 3")
Timber.e("checking Error code: ${billingResult.responseCode}, message: ${productDetailsList}")
}
}
}
fun launchPurchaseFlow(productDetails: ProductDetails) {
val billingFlowParams = BillingFlowParams.newBuilder()
.setProductDetailsParamsList(
listOf(
BillingFlowParams.ProductDetailsParams.newBuilder()
.setProductDetails(productDetails)
.setOfferToken(productDetails.subscriptionOfferDetails?.get(0)?.offerToken!!)
.build()
)
).build()
billingClient.launchBillingFlow(activity, billingFlowParams) // Activity로 구매 플로우 실행
}
}
'Floney' 카테고리의 다른 글
[Floney] 안드로이드 앱 계정 이전 (0) | 2024.11.27 |
---|---|
[Floney] Android Appsflyer 앱링크 설정 (2) | 2024.11.07 |
[Floney] Google Admob 광고 키 숨기기 (디버그키, 릴리즈키) (0) | 2024.10.16 |
[Floney] Android 구글 광고 달기 (보상형 광고, 배너 광고) (1) | 2024.09.07 |
[Floney] 내가 맡은 부분 자체 QA (0) | 2024.06.08 |