Comments (6)
Hello @jorgevalbuena56 ,
Sorry for the lack of instructions. The reason this repo comes with no use instructions is because it was never meant to be used publicly, even though the repo is public.
Nevertheless, I can help you setup it in your own apps.
I see you use @Inject annotation, so I assume you are using Dagger. I'll give examples using Dagger (Hilt) also. But of course you can instantiate everything manually.
First, we need to instantiate BillerImpl
. This module can be seen as the 'core' and is responsible for managing the billing connection, fetching purchases, initiating purchases, etc. This is the class constructor:
@ExperimentalCoroutinesApi
@Singleton
class BillerImpl @Inject constructor(
@ApplicationContext context: Context,
private val acknowledgeableSkus: Set<@JvmSuppressWildcards AcknowledgeableSku>,
private val consumableSkus: Set<@JvmSuppressWildcards ConsumableSku>
) : PurchaseState.Owner {
So, it is a singleton (only one instance of BillerImpl
for the whole app), and we need an Application context
, a set of AcknowledgeableSku
(Skus for which purchases you acknowledge) and ConsumableSku
(skus that you consume after purchasing). You can read more on acknowledging vs consuming purchases on Play Billing documentation.
The application context is automatically provided by Dagger/Hilt. We need to provide the set of AcknowledgeableSku
and ConsumableSku
, and finally, we can instantiate and provide BillerImpl.
Those are my Skus:
@Provides
@Singleton
fun provideSubscriptionSku(): SubscriptionSku = SubscriptionSku()
@Provides
@Singleton
fun providePremiumOnceSku(): PremiumOnceSku = PremiumOnceSku()
@Provides
@Singleton
fun provideAcknowledgeableSkus(
subscriptionSku: SubscriptionSku,
premiumOnceSku: PremiumOnceSku
): Set<AcknowledgeableSku> = setOf(premiumOnceSku, subscriptionSku)
@Provides
@Singleton
fun provideConsumableSkus(): Set<ConsumableSku> = setOf()
I have no consumable skus (empty set), and two acknowledgeable skus. The PremiumOnceSku
is a INAPP sou and SubscriptionSku
is a SUBS sku. Both are data classes that override the interfaces.
data class PremiumOnceSku(
override val sku: String = "com.your.app.premium_pay_once",
override val price: MutableStateFlow<String> = MutableStateFlow(""),
override val type: SkuType = SkuType.INAPP,
override val paymentPeriod: MutableStateFlow<SkuContract.Period> = MutableStateFlow(SkuContract.Period.ONCE)
) : AcknowledgeableSku
Now Dagger/Hilt would already know how to instantiate BillerImpl
, and it could be injected anywhere in your code. But there's rarely any reason for injecting BillerImpl
as itself -- it's better to inject it as its interface PurchaseState.Owner
: this is the interface that says BillerImpl
holds a PurchaseState
object, and PurchaseStateImpl
is the class that implements the PurchaseState
interface. It is just a class that holds the acknowledged
and consumed
purchases.
class PurchaseStateImpl : PurchaseState {
private val _acknowledged = MutableStateFlow<Set<AcknowledgeableSku>>(emptySet())
override val acknowledged: StateFlow<Set<AcknowledgeableSku>> = _acknowledged.asStateFlow()
private val _consumed = MutableSharedFlow<ConsumableSku>()
override val consumed: SharedFlow<ConsumableSku> = _consumed.asSharedFlow()
// ...
}
So we provide PurchaseState.Owner
from BillerImpl
:
@Binds
@Singleton
abstract fun bindPurchaseStateOwner(impl: BillerImpl): PurchaseState.Owner
From this point you can already have use cases to check for current purchases. For example:
class CheckPremiumStatusUseCaseImpl @Inject constructor(
scope: CoroutineScope,
purchaseStateOwner: PurchaseState.Owner,
private val premiumSkus: Set<@JvmSuppressWildcards AcknowledgeableSku>
) : CheckPremiumStatusUseCase {
private val isSubscribed =
purchaseStateOwner
.purchaseState
.acknowledged
.map { skuSet ->
val skus = skuSet.map { it.sku }
premiumSkus.any { it.sku in skus }
}
.stateIn(scope, SharingStarted.Eagerly, false)
override fun invoke(): StateFlow<Boolean> = isSubscribed
}
(note the use case depends on PurchaseState.Owner
and not on BillerImpl
)
If you want to initiate the billing flow, e.g., make a purchase, you need an instance of BillingFlow
. This instance cannot be Singleton as it depends on Activity
. This is a characteristic of the Billing library.
The interface is very simple:
interface BillingFlow {
suspend operator fun invoke(sku: SkuContract): Boolean
}
And the implementation is also simple, all it needs is a BillerImpl
and an Activity
, and all it does is call BillerImpl
with the provided Activity
:
@ExperimentalCoroutinesApi
class BillingFlowImpl @Inject constructor(
private val biller: BillerImpl,
private val activity: Activity
) : BillingFlow {
override suspend fun invoke(sku: SkuContract): Boolean = biller.launchBillingFlow(activity, sku)
}
(note: these interface and implementation class are already provided by the library, you don't need to implement them yourself)
So we can provide BillingFlow
using Dagger/Hilt easily. Hilt already knows by design how to provide Activity
, and after the initial setup we just did it also knows how to provide BillerImpl
, so it knows how to provide all the constructor dependencies, and then it knows how to provide BillingFlowImpl
. All we need to do is bind BillingFlowImpl
implementation to BillingFlow
interface:
@Binds
abstract fun bindBillingFlow(impl: BillingFlowImpl): BillingFlow
And you can use BillingFlow
on your use-cases for initiating purchases. E.g.:
class BuyPremiumUseCaseImpl @Inject constructor(
private val checkPremiumStatusUseCase: CheckPremiumStatusUseCase,
private val billingFlow: BillingFlow,
private val premiumOnceSku: PremiumOnceSku
) : BuyPremiumUseCase {
override suspend fun invoke() {
if (checkPremiumStatusUseCase().value) {
// already owns the Sku
return
}
billingFlow(premiumOnceSku)
}
}
NO DAGGER/HILT
If you do not use Dagger/Hilt, you instantiate manually.
For singletons, you can subclass Application class and hold instances there:
class MyApp : Application {
lateinit var premiumOnceSku: AcknowledgeableSku
lateinit var acknowledgeableSkus: Set<AcknowledgeableSku>
lateinit var consumableSkus: Set<ConsumableSku>
lateinit var billerImpl: BillerImpl
override fun onCreate() {
premiumOnceSku = PremiumOnceSku()
acknowledgeableSkus = setOf(premiumOnceSku)
consumableSkus = setOf()
billerImpl = BillerImpl(this, acknowledgeableSkus, consumableSkus)
}
}
On each Activity that will initiate billing flows, you need to instantiate the BillingFlow
lateinit var billingFlow: BillingFlow
override fun onCreate(savedInstanceState: Bundle?) {
super(savedInstanceState)
billingFlow = BillingFlowImpl((application as MyApp).billerImpl, this)
}
ON VERSIONING
Note: the "release" version is quite old, I don't do GitHub 'releases' anymore. Use the latest commit hash instead:
biller_version = 'd108905c99'
...
implementation "com.github.psteiger.freelapp-biller:$biller_version"
Hope it helps.
from freelapp-biller.
Summing up:
- Create Sku data classes and implement either
AcknowledgeableSku
orConsumableSku
. - Instantiate
BillerImpl
as Singleton (Subclassing Application is an option if not using Dagger/Hilt) - Use
BillerImpl
asPurchaseState.Owner
to observe purchases, and not asBillerImpl
. - Instantiate
BillingFlowImpl
asBillingFlow
onActivity
to start billing flows.
from freelapp-biller.
Hi @psteiger Awesome explanation. I will follow your steps and reach out in case I have road blocks.
Regarding the purchase token that is generated after a purchase is made, Google recommends to store it in our own server to prevent Fraud. Are you doing that? How are you handling that token on your side, if you are doing something with it at all?
Cheers.
from freelapp-biller.
Hi @jorgevalbuena56,
That's a good question -- no, unfortunately in this library I do not deal with storing the token in a server, and indeed this is recommended by Google. I'm planning on that, but feel free to fork the repo and do whichever modifications if you decide to implement that yourself. I also welcome pull requests :)
from freelapp-biller.
no problem @psteiger . I am still analysing if I implement your solution or I continue with https://qonversion.io/. They have done the same as you plus analytics and other features but unfortunately have some issues in the SDK that I am waiting for fixes. Just throwing that option out in case you don't know that platform.
from freelapp-biller.
@jorgevalbuena56 I did not know that platform. Thanks for sharing!
from freelapp-biller.
Related Issues (2)
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from freelapp-biller.