11package ir.cafebazaar.poolakey
22
33import android.app.Activity
4- import android.content.ComponentName
54import android.content.Context
6- import android.content.Intent
7- import android.content.IntentSender
8- import android.content.ServiceConnection
9- import android.os.DeadObjectException
10- import android.os.IBinder
115import androidx.fragment.app.Fragment
12- import com.android.vending.billing.IInAppBillingService
13- import ir.cafebazaar.poolakey.billing.BillingFunction
14- import ir.cafebazaar.poolakey.billing.consume.ConsumeFunctionRequest
15- import ir.cafebazaar.poolakey.billing.purchase.PurchaseFunctionRequest
16- import ir.cafebazaar.poolakey.billing.query.QueryFunctionRequest
6+ import ir.cafebazaar.poolakey.billing.connection.BillingConnectionCommunicator
7+ import ir.cafebazaar.poolakey.billing.connection.ReceiverBillingConnection
8+ import ir.cafebazaar.poolakey.billing.connection.ServiceBillingConnection
9+ import ir.cafebazaar.poolakey.billing.query.QueryFunction
1710import ir.cafebazaar.poolakey.callback.ConnectionCallback
1811import ir.cafebazaar.poolakey.callback.ConsumeCallback
1912import ir.cafebazaar.poolakey.callback.PurchaseIntentCallback
2013import ir.cafebazaar.poolakey.callback.PurchaseQueryCallback
2114import ir.cafebazaar.poolakey.config.PaymentConfiguration
22- import ir.cafebazaar.poolakey.constant.BazaarIntent
23- import ir.cafebazaar.poolakey.constant.Billing
24- import ir.cafebazaar.poolakey.constant.Const
25- import ir.cafebazaar.poolakey.exception.BazaarNotFoundException
26- import ir.cafebazaar.poolakey.exception.DisconnectException
27- import ir.cafebazaar.poolakey.exception.IAPNotSupportedException
28- import ir.cafebazaar.poolakey.exception.SubsNotSupportedException
2915import ir.cafebazaar.poolakey.request.PurchaseRequest
30- import ir.cafebazaar.poolakey.security.Security
3116import ir.cafebazaar.poolakey.thread.PoolakeyThread
3217
3318internal class BillingConnection (
3419 private val context : Context ,
3520 private val paymentConfiguration : PaymentConfiguration ,
3621 private val backgroundThread : PoolakeyThread <Runnable >,
37- private val purchaseFunction : BillingFunction <PurchaseFunctionRequest >,
38- private val consumeFunction : BillingFunction <ConsumeFunctionRequest >,
39- private val queryFunction : BillingFunction <QueryFunctionRequest >
40- ) : ServiceConnection {
22+ private val queryFunction : QueryFunction ,
23+ private val mainThread : PoolakeyThread <() -> Unit >
24+ ) {
4125
4226 private var callback: ConnectionCallback ? = null
4327
44- private var billingService : IInAppBillingService ? = null
28+ private var billingCommunicator : BillingConnectionCommunicator ? = null
4529
4630 internal fun startConnection (connectionCallback : ConnectionCallback .() -> Unit ): Connection {
4731 callback = ConnectionCallback (disconnect = ::stopConnection).apply (connectionCallback)
48- Intent (BILLING_SERVICE_ACTION ).apply { `package` = Const .BAZAAR_PACKAGE_NAME }
49- .takeIf (
50- thisIsTrue = {
51- context.packageManager.queryIntentServices(it, 0 ).isNullOrEmpty().not ()
52- },
53- andIfNot = {
54- callback?.connectionFailed?.invoke(BazaarNotFoundException ())
55- }
56- )?.takeIf (
57- thisIsTrue = {
58- Security .verifyBazaarIsInstalled(context)
59- },
60- andIfNot = {
61- callback?.connectionFailed?.invoke(BazaarNotFoundException ())
62- }
63- )?.also {
64- try {
65- context.bindService(it, this , Context .BIND_AUTO_CREATE )
66- } catch (e: SecurityException ) {
67- callback?.connectionFailed?.invoke(e)
68- }
69- }
70- return requireNotNull(callback)
71- }
7232
73- override fun onServiceConnected (name : ComponentName ? , service : IBinder ? ) {
74- try {
75- IInAppBillingService .Stub .asInterface(service)
76- ?.also { billingService = it }
77- ?.takeIf (
78- thisIsTrue = {
79- isPurchaseTypeSupported(purchaseType = PurchaseType .IN_APP )
80- },
81- andIfNot = {
82- callback?.connectionFailed?.invoke(IAPNotSupportedException ())
83- }
84- )
85- ?.takeIf (
86- thisIsTrue = {
87- ! paymentConfiguration.shouldSupportSubscription || isPurchaseTypeSupported(
88- purchaseType = PurchaseType .SUBSCRIPTION
89- )
90- },
91- andIfNot = {
92- callback?.connectionFailed?.invoke(SubsNotSupportedException ())
93- }
94- )
95- ?.also { callback?.connectionSucceed?.invoke() }
96- } catch (ignored: DeadObjectException ) {
97- // onServiceDisconnected will get called so no need to do anything
98- }
99- }
33+ val serviceCommunicator = ServiceBillingConnection (
34+ context,
35+ mainThread,
36+ backgroundThread,
37+ paymentConfiguration,
38+ queryFunction,
39+ ::disconnect
40+ )
10041
101- private fun isPurchaseTypeSupported (
102- purchaseType : PurchaseType
103- ): Boolean {
104- val supportState = billingService?.isBillingSupported(
105- Billing .IN_APP_BILLING_VERSION ,
106- context.packageName,
107- purchaseType.type
42+ val receiverConnection = ReceiverBillingConnection (
43+ paymentConfiguration,
44+ queryFunction
10845 )
109- return supportState == BazaarIntent .RESPONSE_RESULT_OK
110- }
11146
112- fun purchase (
113- activity : Activity ,
114- purchaseRequest : PurchaseRequest ,
115- purchaseType : PurchaseType ,
116- callback : PurchaseIntentCallback .() -> Unit
117- ) {
47+ val canConnect = serviceCommunicator.startConnection(context, requireNotNull(callback))
11848
119- val intentSenderFire: (IntentSender ) -> Unit = { intentSender ->
120- activity.startIntentSenderForResult(
121- intentSender,
122- purchaseRequest.requestCode,
123- Intent (),
124- 0 ,
125- 0 ,
126- 0
49+ billingCommunicator = if (canConnect) {
50+ serviceCommunicator
51+ } else {
52+ receiverConnection.startConnection(
53+ context,
54+ requireNotNull(callback)
12755 )
128- PurchaseIntentCallback ().apply (callback).purchaseFlowBegan.invoke()
129- }
13056
131- val intentFire: (Intent ) -> Unit = { intent ->
132- activity.startActivityForResult(
133- intent,
134- purchaseRequest.requestCode
135- )
136- PurchaseIntentCallback ().apply (callback).purchaseFlowBegan.invoke()
57+ receiverConnection
13758 }
138-
139- purchase(purchaseRequest, purchaseType, callback, intentSenderFire, intentFire)
59+ return requireNotNull(callback)
14060 }
14161
14262 fun purchase (
143- fragment : Fragment ,
63+ activity : Activity ,
14464 purchaseRequest : PurchaseRequest ,
14565 purchaseType : PurchaseType ,
14666 callback : PurchaseIntentCallback .() -> Unit
14767 ) {
148- val intentSenderFire: (IntentSender ) -> Unit = { intentSender ->
149- fragment.startIntentSenderForResult(
150- intentSender,
151- purchaseRequest.requestCode,
152- Intent (),
153- 0 ,
154- 0 ,
155- 0 ,
156- null
157- )
158- PurchaseIntentCallback ().apply (callback).purchaseFlowBegan.invoke()
159- }
160-
161- val intentFire: (Intent ) -> Unit = { intent ->
162- fragment.startActivityForResult(
163- intent,
164- purchaseRequest.requestCode
68+ runOnCommunicator(TAG_PURCHASE ) {
69+ requireNotNull(billingCommunicator).purchase(
70+ activity,
71+ purchaseRequest,
72+ purchaseType,
73+ callback
16574 )
166- PurchaseIntentCallback ().apply (callback).purchaseFlowBegan.invoke()
16775 }
168-
169- purchase(purchaseRequest, purchaseType, callback, intentSenderFire, intentFire)
17076 }
17177
172- private fun purchase (
78+ fun purchase (
79+ fragment : Fragment ,
17380 purchaseRequest : PurchaseRequest ,
17481 purchaseType : PurchaseType ,
175- callback : PurchaseIntentCallback .() -> Unit ,
176- fireIntentSender : (IntentSender ) -> Unit ,
177- fireIntent : (Intent ) -> Unit
178- ) = withService {
179- purchaseFunction.function(
180- billingService = this ,
181- request = PurchaseFunctionRequest (
82+ callback : PurchaseIntentCallback .() -> Unit
83+ ) {
84+ runOnCommunicator(TAG_PURCHASE ) {
85+ requireNotNull(billingCommunicator).purchase(
86+ fragment,
18287 purchaseRequest,
18388 purchaseType,
184- callback,
185- fireIntentSender,
186- fireIntent
89+ callback
18790 )
188- )
189- } ifServiceIsDisconnected {
190- PurchaseIntentCallback ().apply (callback).failedToBeginFlow.invoke(DisconnectException ())
91+ }
19192 }
19293
19394 fun consume (
19495 purchaseToken : String ,
19596 callback : ConsumeCallback .() -> Unit
196- ) = withService(runOnBackground = true ) {
197- consumeFunction.function(
198- billingService = this ,
199- request = ConsumeFunctionRequest ( purchaseToken, callback)
200- )
201- } ifServiceIsDisconnected {
202- ConsumeCallback (). apply (callback).consumeFailed.invoke( DisconnectException ())
97+ ) {
98+ runOnCommunicator( TAG_CONSUME ) {
99+ requireNotNull(billingCommunicator).consume(
100+ purchaseToken,
101+ callback
102+ )
103+ }
203104 }
204105
205106 fun queryPurchasedProducts (
206107 purchaseType : PurchaseType ,
207108 callback : PurchaseQueryCallback .() -> Unit
208- ) = withService(runOnBackground = true ) {
209- queryFunction.function(
210- billingService = this ,
211- request = QueryFunctionRequest ( purchaseType, callback)
212- )
213- } ifServiceIsDisconnected {
214- PurchaseQueryCallback (). apply (callback).queryFailed.invoke( DisconnectException ())
109+ ) {
110+ runOnCommunicator( TAG_QUERY_PURCHASE_PRODUCT ) {
111+ requireNotNull(billingCommunicator).queryPurchasedProducts(
112+ purchaseType,
113+ callback
114+ )
115+ }
215116 }
216117
217118 private fun stopConnection () {
218- if (billingService != null ) {
219- context.unbindService( this )
119+ runOnCommunicator( TAG_STOP_CONNECTION ) {
120+ requireNotNull(billingCommunicator).stopConnection( )
220121 disconnect()
221122 }
222123 }
223124
224- override fun onServiceDisconnected (name : ComponentName ? ) {
225- disconnect()
226- }
227-
228125 private fun disconnect () {
229- billingService = null
230126 callback?.disconnected?.invoke()
231127 callback = null
232128 backgroundThread.dispose()
129+ billingCommunicator = null
233130 }
234131
235- private inline fun withService (
236- runOnBackground : Boolean = false,
237- crossinline service : IInAppBillingService .() -> Unit
238- ): ConnectionState {
239- return billingService?.also {
240- if (runOnBackground) {
241- backgroundThread.execute(Runnable { service.invoke(it) })
242- } else {
243- service.invoke(it)
244- }
245- }?.let { ConnectionState .Connected }
246- ? : run { ConnectionState .Disconnected }
132+ private fun runOnCommunicator (
133+ methodName : String ,
134+ ifConnected : () -> Unit
135+ ) {
136+ if (billingCommunicator == null ) {
137+ raiseErrorForCommunicatorNotInitialized(methodName)
138+ } else {
139+ ifConnected.invoke()
140+ }
247141 }
248142
249- private inline infix fun ConnectionState. ifServiceIsDisconnected ( block : () -> Unit ) {
250- if ( this is ConnectionState . Disconnected ) {
251- block.invoke( )
252- }
143+ private fun raiseErrorForCommunicatorNotInitialized ( methodName : String ) {
144+ callback?.connectionFailed?.invoke(
145+ IllegalStateException ( " You called $methodName but communicator is not initialized yet " )
146+ )
253147 }
254148
255149 companion object {
256- private const val BILLING_SERVICE_ACTION = " ir.cafebazaar.pardakht.InAppBillingService.BIND"
150+ private const val TAG_STOP_CONNECTION = " stopConnection"
151+ private const val TAG_QUERY_PURCHASE_PRODUCT = " queryPurchasedProducts"
152+ private const val TAG_CONSUME = " consume"
153+ private const val TAG_PURCHASE = " purchase"
257154 }
258- }
155+ }
0 commit comments