GithubHelp home page GithubHelp logo

Comments (6)

psteiger avatar psteiger commented on August 19, 2024

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.

psteiger avatar psteiger commented on August 19, 2024

Summing up:

  1. Create Sku data classes and implement either AcknowledgeableSku or ConsumableSku.
  2. Instantiate BillerImpl as Singleton (Subclassing Application is an option if not using Dagger/Hilt)
  3. Use BillerImpl as PurchaseState.Owner to observe purchases, and not as BillerImpl.
  4. Instantiate BillingFlowImpl as BillingFlow on Activity to start billing flows.

from freelapp-biller.

jorgevalbuena56 avatar jorgevalbuena56 commented on August 19, 2024

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.

psteiger avatar psteiger commented on August 19, 2024

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.

jorgevalbuena56 avatar jorgevalbuena56 commented on August 19, 2024

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.

psteiger avatar psteiger commented on August 19, 2024

@jorgevalbuena56 I did not know that platform. Thanks for sharing!

from freelapp-biller.

Related Issues (2)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.