구글 결제 예제인 TrivialDriveKotlin 을 분석해본다.
1. 파일 리스트
2. 네비게이션 그래프
BillingRepository.kt
"user" 는 앱을 다운로드 받고 결재하거나 구독하는 사람들을 의미
"client"는 결재 API를 사용하는 developer/engineer를 의미
Google Play 결제 구현은 Google Play 결제 서비스, 보안 서버, 앱의 자체 로컬 캐시라는 세 가지 데이터 소스의 구매 확인 데이터에 따라 달라집니다.
나머지 앱이 이 데이터에 액세스 할 수 있도록 이러한 다양한 데이터 소스 및 관련 작업을 관리하려면 리포지토리 모듈이 필요합니다.
이 모듈을 [BillingRepository]라고합니다. 그리고이 모듈은 _Play 결제 라이브러리 __Play Billing Library_, 보안 서버의 결제 부분, 앱 로컬 캐시의 결제 부분과의 모든 상호 작용을 처리합니다.
나머지 앱이 가지는 모듈은 다음을 포함하는 ViewModel 형태의 API입니다.
인벤토리 목록 (예 : 판매 대상); 구매 기능 (즉, 구매 방법) 및 일련의 권한 (즉, 사용자가 소유 한 것).
이 권장되는 구조는 Google Play 결제의 복잡성을 일부 제거합니다.
관리되는 제품, 소모품, 구독 및 기타 제품 유형은 통합 API를 통해 동일하게 처리됩니다.
이것이 의미하는 바는 모든 작업이 이제 하나의 모듈 인 [BillingRepository]에 집중되어 있다는 것입니다.
이 저장소는 앱이 판매하는 모든 것과 앱이 판매하는 방법을 추적합니다.
앱은 이 저장소에 나열되지 않은 것을 판매해서는 안됩니다.
따라서 [BillingRepository] 구현을 담당하는 엔지니어는 앱의 비즈니스 모델을 완전히 이해해야 합니다.
결재 flow는 MVVM 구조인 위와 같은 구조로 구성된다.
데이터 다이나믹스 :
앱의 결제 저장소만큼 중요한 기능을 설계 할 때 데이터 메커니즘을 고려하는 것이 중요합니다.
청구 데이터가있는 위치와 리포지토리를 통한 흐름은 설계의 핵심 측면입니다.
Google Play 결제 라이브러리 2.0 이상 (PBL 2.0+)을 사용하면 다음과 같은 세 가지 기본 결제 구현을 뚜렷하게 구분할 수 있습니다.
1- 자격에 대한 오프라인 액세스없이 결제 통합
2- 일부 권한에 대한 오프라인 액세스와 서버 의존 청구 통합
3- 서버리스 결제 통합
이 세 가지 기본 통합에는 가능한 결합 파생물이 있지만 앞으로 몇 주 내에 이러한 각 기본 통합을 해결하는 샘플 또는 코드 랩을 제공 할 것입니다.
다음 세 그림은 각 모델을 나타냅니다.
1- 자격에 대한 오프라인 액세스없이 결제 통합
2. 일부 권한에 대한 오프라인 액세스와 서버 의존 청구 통합
3. 서버리스 결제 통합
위의 [1]와 [2]은 구매를 처리하고 원격 서버에서 권한을 지불합니다.
그러나 [3]은 서버리스이므로 Android 앱 내부의 모든 것을 처리합니다.
이 현재 분석하는 샘플은 [3]에 묘사 된 흐름을 따릅니다.
* 다음은 [3]에 표시된 흐름에 대한 자세한 내용입니다.
1. [launchBillingFlow] 및 [queryPurchasesAsync]는 클라이언트에서 직접 호출 할 수 있습니다.
[launchBillingFlow]는 사용자가 구매를 원할 때 버튼 클릭으로 트리거 될 수 있습니다.
[queryPurchasesAsync]는 앱이 시작될 때 pull-to-refresh 또는 [Activity] 수명주기 이벤트에 의해 트리거 될 수 있습니다. 따라서 그들은 프로세스의 시작점입니다.
2. [onPurchasesUpdated]는 [launchBillingFlow] [BillingClient.launchBillingFlow] 호출에 대한 응답으로 Play [BillingClient]가 호출하는 콜백입니다.
응답 코드가 [BillingClient.BillingResponseCode.OK]이면 개발자는 바로 [processPurchases]로 이동할 수 있습니다.
그러나 응답 코드가 [BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED] 인 경우 개발자는 [queryPurchasesAsync]를 호출하여 처리해야하는 다른 이미 소유 된 항목이 있는지 확인해야합니다.
3. [queryPurchasesAsync] 메서드는 이 사용자의 모든 활성 구매를 가져와이 앱 인스턴스에서 사용할 수 있도록합니다. Play의 [BillingClient]를 여러 번 호출하는 것은 비교적 저렴합니다.
Play는 자체 로컬 캐시에 데이터를 저장하므로 네트워크 호출이 필요하지 않습니다.
구매 데이터는 [processed] [processPurchases]가 되고 [premium contents][Entitlement]으로 변환됩니다.
4. 마지막으로, [BillingRepository]의 공용 인터페이스 (즉, [BillingViewModel])의 일부로 끝나는 모든 데이터는 앱의 다른 부분에서 즉시 로컬 캐시 청구 클라이언트에서 제공됩니다. 로컬 캐시는 [Room] 데이터베이스에 의해 지원되며 클라이언트에 표시되는 모든 데이터는 [LiveData]에 래핑되어 변경 사항이 발생하는 즉시 클라이언트에 반영됩니다.
* 최종 정리 **
앱에서 [1] 또는 [2]를 구현하는 것이 좋습니다.
그러나 [3] 구현은 아직 서버 구성 요소가 없고 Google Play 결제 용으로만 생성하지 않으려는 앱에 대해 제공됩니다.
여기에 제시된 아키텍처와 대부분의 코드는 재사용 가능성이 높지만이 저장소는 앱별로 다릅니다.
예를 들어, Trivial Drive의 경우 프리미엄 자동차, 운전 용 가스, 골드 등급의 세 가지 품목을 판매하도록 맞춤화되었습니다.
따라서 이 저장소는 사용자가 프리미엄 자동차를 소유하거나 가스를 구매하는 것이 의미하는 바를 처리하는 로직을 처리해야합니다.
[BillingClient]는 Google Play 스토어를 통해 이루어진 모든 구매에 대한 가장 신뢰할 수있는 주요 정보 출처입니다.
Play 스토어는 데이터를 보호하기 위해 보안 예방 조치를 취합니다. 또한 대부분의 경우 데이터를 오프라인으로 사용할 수 있으므로 [BillingClient]를 사용하여 구매를 확인하는 데 네트워크 요금이 부과되지 않습니다.
오프라인 부분은 Play 스토어가 사용자가 소유 한 모든 구매를 [최종 일관된 방식](https://developer.android.com/google/play/billing/billing_library_overview#Keep-up-to-date)으로 캐시하기 때문입니다.
이것은 앱이 실제로 Android에 있어야하는 유일한 청구 클라이언트입니다.
나머지 두 개 (webServerBillingClient 및 localCacheBillingClient)는 선택 사항입니다.
추가사항) [playStoreBillingClient]에 대한 연결은 applicationContext를 사용하여 생성됩니다.
이는 인스턴스가 [Activity]에 한정되지 않음을 의미합니다. 또한 비싸지 않기 때문에 전체 [Application]의 수명 동안 열려 있을 수 있습니다.
따라서 각 [Activity] 또는 [Fragment]에 대해 (재) 생성되는지 또는 응용 프로그램의 수명 동안 열려 있는지 여부는 선택의 문제입니다.
로컬 캐시 결제 클라이언트는 업데이트 중에 Play 스토어를 일시적으로 사용하지 못할수 있다는 점에서 중요합니다.
이러한 경우 사용자가 자신이 소유한 프리미엄 데이터에 계속 액세스하는 것이 중요할 수 있습니다.
또는 프리미엄 콘텐츠에 대한 오프라인 액세스를 제공하지 않도록 선택할 수 있습니다.
그러나 프리미엄 콘텐츠에 대한 오프라인 액세스 외에도 로컬 캐시 청구 클라이언트는 특정 트랜잭션을 더 쉽게 만듭니다.
예를 들어 오프라인 캐시 결제 클라이언트가 없으면, 앱에서 소모품을 처리하기 위해 보안 서버와 Play 결제 클라이언트를 모두 사용할 수 있어야합니다.
여기에 있는 데이터는 Google Play 스토어에있는 내용을 반영하도록 정기적으로 새로 고침되어야 합니다.
(참고 : LocalBillingDb)
목록은 클라이언트(개발자)에게 판매 가능한 구독을 알려줍니다.
목록은 고객에게 판매 가능한 인앱 상품을 알려줍니다.
위는 LocalBillingDB를 활성화 시킨다.
// START list of each distinct item user may own (i.e. entitlements)
사용자가 소유 할 수있는 각 개별 항목의 목록을 시작합니다 (예 : 권한).
클라이언트는 "무엇을 판매할 지"와 "사용자가 액세스 할 수있는 정보"의 두 가지 유형에 관심이 있습니다. subsSkuDetailsListLiveData 및 inappSkuDetailsListLiveData는 클라이언트에게 "무엇을 판매할 지"를 알려줍니다.
gasTankLiveData, premiumCarLiveData 및 goldStatusLiveData는 사용자에게 어떤 권한이 있는지 클라이언트에 알려줍니다.
이러한 항목에 대해 청구와 관련된 것은 없습니다.
앱의 속성 일뿐입니다.
따라서 이러한 항목을 앱의 나머지 부분에 노출한다고해서 클라이언트가 청구 방식을 이해해야한다는 의미는 아닙니다.
권장되지 않는 한 가지 접근 방식은 클라이언트에게 구매 목록을 제공하고 거기에서 알아내는 것입니다.
그러나 이 경우 [BillingRepository] API는 클라이언트 친화적이지 않습니다. 대신 클라이언트에 대한 각 항목을 지정해야합니다.
예를 들어,이 샘플 앱은 가스, 자동차 및 금 상태의 세 가지 항목을 판매합니다.
따라서 나머지 앱은 항상 다음과 같은 상황을 알고 싶어합니다.
사용자가 보유한 가스의 양; 사용자가 소유 한 자동차 ,사용자의 골드 상태
추가 구현 세부 정보를 노출 할 필요가 없습니다.
또한 적절한 UI가 자동으로 업데이트되도록 각 항목을 [LiveData]로 제공해야합니다.
사용자가 소유 한 가스의 양을 추적합니다.
이것은 [LiveData] 항목이기 때문에 고객은이 사용자에게 구매가 주어지면 즉시 알 수 있습니다.
__ 중요 사항 : __
기술적으로 간단히`val gasLevel : LiveData <Int>`를 클라이언트에 반환하고 클라이언트가 사용 방법을 알아 내도록 할 수 있습니다.
[GasTank]라는 사용자 지정 데이터 유형을 반환하도록 선택할 수 있습니다.
이 앱에서 가스 수준은 하한이 0이고 상한이 4라는 사실을 캡슐화합니다.
이러한 방식으로 클라이언트는 사용자가 구매할 수 없는 시기와 사용자가 구매해야하는시기를 정확히 알 수 있습니다.
이 사용자가 프리미엄 자동차를 이용할 수 있는지 여부를 추적합니다.
이 호출은 앱의 자체 로컬 DB에서 데이터를 반환합니다.
이렇게하면 Play 및 보안 서버를 사용할 수 없는 경우에도 사용자는 구매 한 기능에 계속 액세스 할 수 있습니다.
일반적으로 이것은 항상 최신 상태인지 확인하기 위해 로컬 캐시를 업데이트하는 좋은 장소입니다.
그러나 onBillingSetupFinished는 이미 queryPurchasesAsync를 호출했습니다. 그래서 필요 없습니다.
상관 관계가 있는 데이터 소스는 리포지토리 모듈 내에 속하므로 나머지 앱은 필요한 데이터에 적절하게 액세스 할 수 있습니다. 그래도 수명주기 이벤트를 기반으로 데이터 소스 연결의 열기 (때로는 닫기)를 추적하는 것이 효과적 일 수 있습니다.
이렇게하는 한 가지 편리한 방법은 [BillingViewModel]이 인스턴스화 될 때, 이 [startDataSourceConnections]를 호출하고 [ViewModel.onCleared] 내에서 [endDataSourceConnections]를 호출하는 것입니다.
Play [BillingClient]에 성공적으로 연결되었을 때의 콜백입니다.
이 시점에서 [SkuDetails] 및 [Purchases] [Purchase]를 가져 오는 것이 좋습니다.
이 메서드는 앱이 실수로 [BillingClient]에서 연결 해제 된 경우 호출됩니다.
재시도 정책을 사용하여 재 연결을 시도해야합니다.
[endConnection] [BillingClient.endConnection]과 연결 끊김의 차이점에 유의하십시오.
-연결 해제 됨은 다시 연결해도 괜찮다는 의미입니다.
-endConnection은 endConnection이 호출 된 후 [BillingClient] 인스턴스가 유효하지 않기 때문에 [playStoreBillingClient]를 다시 인스턴스화 한 다음 새 연결을 시작해야 함을 의미합니다.
-백 그라운드
Google Play 결제에서는 영수증을 [Purchases][Purchase]라고 합니다.
따라서 사용자가 무언가를 구매하면 Play Billing은 앱에게 [Purchase] 개체를 리턴하는데, 이것은 사용자에게 [Entitlement]를 릴리즈하는 데 사용한다.
영수증은 [BillingRepository] 내에서 중요합니다. 그러나 그들은 클라이언트가 이에 대해 알 필요가 없기 때문에 저장소의 공개 API에 속하지 않습니다.
권한 해제시기는 구매 유형에 따라 다릅니다.
소모품의 경우 Google Play에서 소비 할 때까지 출시가 연기 될 수 있습니다.
비 소모성 제품 및 구독의 경우 [BillingClient.acknowledgePurchaseAsync]가 호출 될 때까지 릴리스가 연기 될 수 있습니다.
보안을 강화하고 일부 트랜잭션을 더 쉽게 만들기 위해 영수증을 로컬 캐시에 보관해야합니다.
이 방법 [이 메서드] [queryPurchasesAsync]는이 사용자의 모든 활성 구매를 가져와이 앱 인스턴스에서 사용할 수 있도록합니다.
이 메서드는 결제 시스템에서 중심적인 역할을하지만 사용자가 앱을 시작할 때와 같은 중요한 시점에서 호출해야합니다.
구매 데이터는 앱의 나머지 부분에 중요하므로 [BillingViewModel]이 Play [BillingClient]와 성공적으로 연결될 때마다이 메서드가 호출됩니다.
통화는 [onBillingSetupFinished]를 통해 이루어집니다.
또한 [3]에서이 메서드는 구매가 "이미 소유"된 경우 [onPurchasesUpdated] 내부에서 호출되며 사용자가 다른 장치에서 거의 같은 시간에 항목을 구매할 경우 발생할 수 있습니다.
Google Play 결제는 두 가지 SKU 유형 만 지원합니다.
[in-app products][BillingClient.SkuType.INAPP] 및 [subscriptions][BillingClient.SkuType.SUBS].
인앱 상품은 집이나 음식과 같이 사용자가 구매할 수있는 실제 항목입니다.
구독은 자동 보험과 같이 사용자가 정기적으로 지불해야하는 서비스를 의미합니다.
구독은 소비되지 않습니다.
Play Billing은 앱이 사용자가 집과 같이 영원히 보관할 (즉 절대로 소비하지 않는) 항목과 음식과 같이 사용자가 계속 구매해야하는 소모품을 판매 할 수 있음을 이해하기 때문에 인앱 상품을 소비하는 방법을 제공합니다.
그럼에도 불구하고 Google Play는 인앱 상품이 전적으로 귀하에게 달려 있다는 구분을 남깁니다.
앱에서 사용자가 항목을 계속 구매할 수 있도록하려면 구매할 때마다 [BillingClient.consumeAsync]를 호출해야합니다.
이는 Google Play에서 사용자가 이전에 구매했지만 소비하지 않은 항목을 구매하도록 허용하지 않기 때문입니다.
예를 들어 Trivial Drive에서는 사용자가 가스를 구입할 때마다 consumerAsync가 호출됩니다.
그렇지 않으면 탱크가 비워지면 연료를 사거나 다시 운전할 수 없습니다.
구매를 확인하지 않으면 Google Play 스토어에서 거래 후 며칠 이내에 사용자에게 환불을 제공합니다.
따라서 앱 내에서 [BillingClient.acknowledgePurchaseAsync]를 구현해야합니다.
구매 / 영수증이 프리미엄 콘텐츠로 전환되는 마지막 단계입니다.
이 샘플에서는 자격이 지불되면 영수증이 폐기됩니다.
이상적으로 구현은 보안 서버로 구성되 어이 검사를 불필요하게 만듭니다.
[Security] 모듈을 참고하라.
구독이 서포팅되는 기기인지를 테스트한다.
Google Play 개발자 콘솔에 SKU 집합이 정의되어 있을 것입니다.
이 방법은 해당 SKU의 (부적절한) 하위 집합을 요청하기위한 것입니다.
따라서 이 메서드는 제품 ID 목록을 수락하고 일치하는 SkuDetails 목록을 반환합니다.
결과는 [onSkuDetailsResponse]에 전달됩니다.
사용자가 구매를 원할 때 호출하는 기능입니다.
이 함수는 Google Play 결제 흐름을 시작합니다.
이 호출에 대한 응답은 [onPurchasesUpdated]에 반환됩니다.
이 샘플 앱은 구독 항목 인 GoldStatus를 하나만 제공합니다.
그리고 사용자가 GoldStatus를 구독 할 수있는 방법에는 월간 또는 연간의 두 가지가 있습니다.
BillingRepository는 이전 SKU가있는 경우 액세스 할 수 있습니다.
이 메소드는 신규 구매가 감지되면 [playStoreBillingClient]에서 호출됩니다.
이 메소드의 구매 목록은 [queryPurchases] [BillingClient.queryPurchases]의 구매 목록과 동일하지 않습니다. queryPurchases는이 사용자가 소유 한 모든 항목을 반환하는 반면 [onPurchasesUpdated]는 방금 구입했거나 청구 된 항목 만 반환합니다.
여기에 제공된 구매는 [인증] (https://developer.android.com/google/play/billing/billing_library_overview#Verify) 및 보관을 위해 보안 서버로 전달되어야합니다. 그리고 이 구매가 소모품이라면 소비해야하며 보안 서버에 소비에 대해 알려야합니다. 이 모든 작업은 [queryPurchasesAsync]를 호출하여 수행됩니다.
Play가 onConsumeResponse를 호출했기 때문에이 disburseConsumableEntitlements 메소드가 호출되었습니다.
따라서 구매를 영수증으로 생각하면 사용자가 방금 제품을 소비했기 때문에 더 이상 영수증 사본을 로컬 캐시에 보관할 필요가 없습니다.
가스 레벨은 사용자가 운전할 때 클라이언트에서 업데이트하거나 사용자가 더 많은 가스를 구매할 때 데이터 소스 (예 : Play BillingClient)에서 업데이트 할 수 있습니다.
따라서 이 리포지토리는 경쟁 조건과 인터리브를 감시해야합니다.
싱글턴.
프라이빗 객체
사용자 정의가 필요하지 않은 경우 여기에서 이러한 목록을 정의하고 하드 코딩 할 수 있습니다.
즉, 사용자 지정이 필요한 사용 사례가 있습니다.
- SKU를 변경할 때마다 APK (또는 번들)를 업데이트하지 않으려면 보안 서버에서 이러한 목록을로드 할 수 있습니다.
- 사용자가 다른 활동 또는 조각에서 다른 항목을 구입할 수있는 디자인 인 경우 각 하위 집합에 대한 목록을 정의 할 수 있습니다. INAPP_SKUS 및 SUBS_SKUS의 두 하위 집합 만 있습니다.
--------------------------------------------------------------------------------------------------------------------------------
BillingRepository.kt 끝
--------------------------------------------------------------------------------------------------------------------------------
'Android Dev > Google Billing API' 카테고리의 다른 글
구글 결제 시스템 Billing API 설계 절차(4) - TrivialDriveKotlin 분석 - 동작 메커니즘. (0) | 2020.12.27 |
---|---|
구글 결제 시스템 Billing API 설계 절차(2) (0) | 2020.12.21 |
구글 결제 시스템 Billing API 설계 절차(1) (0) | 2020.12.21 |