Skip to content

Commit

Permalink
Refactor BillingClientWrapper (#4253)
Browse files Browse the repository at this point in the history
<!--
Note: This checklist is a reminder of our shared engineering
expectations.
The items in Bold are required
If your PR involves UI changes:
1. Upload screenshots or screencasts that illustrate the changes before
/ after
2. Add them under the UI changes section (feel free to add more columns
if needed)
If your PR does not involve UI changes, you can remove the **UI
changes** section

At a minimum, make sure your changes are tested in API 23 and one of the
more recent API levels available.
-->

Task/Issue URL:
https://app.asana.com/0/1205648422731273/1206760049330313/f

### Description

See task.

### No UI changes
  • Loading branch information
lmac012 authored Mar 7, 2024
1 parent 9718334 commit ed98934
Show file tree
Hide file tree
Showing 10 changed files with 694 additions and 370 deletions.
1 change: 1 addition & 0 deletions subscriptions/subscriptions-impl/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ dependencies {
testImplementation AndroidX.core
testImplementation AndroidX.test.ext.junit
testImplementation "androidx.test:runner:_"
testImplementation "androidx.lifecycle:lifecycle-runtime-testing:_"
testImplementation Testing.robolectric
testImplementation 'app.cash.turbine:turbine:_'
testImplementation project(path: ':common-test')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import com.duckduckgo.subscriptions.impl.SubscriptionStatus.Inactive
import com.duckduckgo.subscriptions.impl.SubscriptionStatus.NotAutoRenewable
import com.duckduckgo.subscriptions.impl.SubscriptionStatus.Unknown
import com.duckduckgo.subscriptions.impl.SubscriptionsData.*
import com.duckduckgo.subscriptions.impl.billing.BillingClientWrapper
import com.duckduckgo.subscriptions.impl.billing.PlayBillingManager
import com.duckduckgo.subscriptions.impl.billing.PurchaseState
import com.duckduckgo.subscriptions.impl.pixels.SubscriptionPixelSender
import com.duckduckgo.subscriptions.impl.repository.AuthRepository
Expand Down Expand Up @@ -146,7 +146,7 @@ class RealSubscriptionsManager @Inject constructor(
private val authService: AuthService,
private val subscriptionsService: SubscriptionsService,
private val authRepository: AuthRepository,
private val billingClientWrapper: BillingClientWrapper,
private val playBillingManager: PlayBillingManager,
private val emailManager: EmailManager,
private val context: Context,
@AppCoroutineScope private val coroutineScope: CoroutineScope,
Expand Down Expand Up @@ -177,7 +177,7 @@ class RealSubscriptionsManager @Inject constructor(
private suspend fun emitCurrentPurchaseValues() {
purchaseStateJob?.cancel()
purchaseStateJob = coroutineScope.launch(dispatcherProvider.io()) {
billingClientWrapper.purchaseState.collect {
playBillingManager.purchaseState.collect {
when (it) {
is PurchaseState.Purchased -> checkPurchase(it.packageName, it.purchaseToken)
else -> {
Expand Down Expand Up @@ -356,7 +356,7 @@ class RealSubscriptionsManager @Inject constructor(

override suspend fun recoverSubscriptionFromStore(): SubscriptionsData {
return try {
val purchase = billingClientWrapper.purchaseHistory.lastOrNull()
val purchase = playBillingManager.purchaseHistory.lastOrNull()
return if (purchase != null) {
val signature = purchase.signature
val body = purchase.originalJson
Expand Down Expand Up @@ -404,16 +404,15 @@ class RealSubscriptionsManager @Inject constructor(
when (val response = prePurchaseFlow()) {
is Success -> {
if (response.entitlements.isEmpty()) {
val billingParams = billingClientWrapper.billingFlowParamsBuilder(
productDetails = productDetails,
offerToken = offerToken,
externalId = response.externalId,
isReset = isReset,
).build()
logcat(LogPriority.DEBUG) { "Subs: external id is ${response.externalId}" }
_currentPurchaseState.emit(CurrentPurchase.PreFlowFinished)
withContext(dispatcherProvider.main()) {
billingClientWrapper.launchBillingFlow(activity, billingParams)
playBillingManager.launchBillingFlow(
activity = activity,
productDetails = productDetails,
offerToken = offerToken,
externalId = response.externalId,
)
}
} else {
pixelSender.reportRestoreAfterPurchaseAttemptSuccess()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* Copyright (c) 2024 DuckDuckGo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.duckduckgo.subscriptions.impl.billing

import android.app.Activity
import com.android.billingclient.api.ProductDetails
import com.android.billingclient.api.PurchaseHistoryRecord

interface BillingClientAdapter {
val ready: Boolean

suspend fun connect(
purchasesListener: (PurchasesUpdateResult) -> Unit,
disconnectionListener: () -> Unit,
): BillingInitResult

suspend fun getSubscriptions(productIds: List<String>): SubscriptionsResult

suspend fun getSubscriptionsPurchaseHistory(): SubscriptionsPurchaseHistoryResult

suspend fun launchBillingFlow(
activity: Activity,
productDetails: ProductDetails,
offerToken: String,
externalId: String,
): LaunchBillingFlowResult
}

sealed class BillingInitResult {
data object Success : BillingInitResult()
data object Failure : BillingInitResult()
}

sealed class SubscriptionsResult {
data class Success(val products: List<ProductDetails>) : SubscriptionsResult()

data class Failure(
val billingResponseCode: Int? = null,
val debugMessage: String? = null,
) : SubscriptionsResult()
}

sealed class SubscriptionsPurchaseHistoryResult {
data class Success(val history: List<PurchaseHistoryRecord>) : SubscriptionsPurchaseHistoryResult()
data object Failure : SubscriptionsPurchaseHistoryResult()
}

sealed class LaunchBillingFlowResult {
data object Success : LaunchBillingFlowResult()
data object Failure : LaunchBillingFlowResult()
}

sealed class PurchasesUpdateResult {
data class PurchasePresent(
val purchaseToken: String,
val packageName: String,
) : PurchasesUpdateResult()

data object PurchaseAbsent : PurchasesUpdateResult()
data object UserCancelled : PurchasesUpdateResult()
data object Failure : PurchasesUpdateResult()
}
Loading

0 comments on commit ed98934

Please sign in to comment.