11package dev.hyo.openiap.store
22
33import dev.hyo.openiap.ActiveSubscription
4+ import dev.hyo.openiap.AndroidSubscriptionOfferInput
45import dev.hyo.openiap.DeepLinkOptions
56import dev.hyo.openiap.FetchProductsResult
67import dev.hyo.openiap.FetchProductsResultProducts
@@ -22,6 +23,13 @@ import dev.hyo.openiap.RequestSubscriptionAndroidProps
2223import dev.hyo.openiap.RequestSubscriptionPropsByPlatforms
2324import dev.hyo.openiap.RequestPurchaseResultPurchase
2425import dev.hyo.openiap.RequestPurchaseResultPurchases
26+ import dev.hyo.openiap.RequestPurchaseResult
27+ import dev.hyo.openiap.MutationRequestPurchaseHandler
28+ import dev.hyo.openiap.QueryFetchProductsHandler
29+ import dev.hyo.openiap.QueryGetAvailablePurchasesHandler
30+ import dev.hyo.openiap.MutationFinishTransactionHandler
31+ import dev.hyo.openiap.MutationInitConnectionHandler
32+ import dev.hyo.openiap.MutationEndConnectionHandler
2533import android.app.Activity
2634import android.content.Context
2735import com.android.billingclient.api.BillingClient
@@ -132,13 +140,20 @@ class OpenIapStore(private val module: OpenIapModule) {
132140 }
133141
134142 // -------------------------------------------------------------------------
135- // Connection Management
143+ // Connection Management - Using GraphQL handler pattern
136144 // -------------------------------------------------------------------------
137- suspend fun initConnection (): Boolean {
145+ val initConnection: MutationInitConnectionHandler = {
138146 setLoading { it.initConnection = true }
139- return try {
147+ try {
140148 val ok = module.initConnection()
141149 _isConnected .value = ok
150+
151+ // Add listeners when connected
152+ if (ok) {
153+ addPurchaseUpdateListener(purchaseUpdateListener)
154+ addPurchaseErrorListener(purchaseErrorListener)
155+ }
156+
142157 ok
143158 } catch (e: Exception ) {
144159 setError(e.message)
@@ -148,27 +163,28 @@ class OpenIapStore(private val module: OpenIapModule) {
148163 }
149164 }
150165
151- suspend fun endConnection (): Boolean {
152- return try {
166+ val endConnection: MutationEndConnectionHandler = {
167+ removePurchaseUpdateListener(purchaseUpdateListener)
168+ removePurchaseErrorListener(purchaseErrorListener)
169+ try {
153170 val ok = module.endConnection()
154171 _isConnected .value = false
172+ clear()
155173 ok
156174 } catch (e: Exception ) {
157175 setError(e.message)
158176 throw e
159177 }
160178 }
161179
180+
162181 // -------------------------------------------------------------------------
163- // Product Management
182+ // Product Management - Using GraphQL handler pattern
164183 // -------------------------------------------------------------------------
165- suspend fun fetchProducts (
166- skus : List <String >,
167- type : ProductQueryType = ProductQueryType .All
168- ): FetchProductsResult {
184+ val fetchProducts: QueryFetchProductsHandler = { request ->
169185 setLoading { it.fetchProducts = true }
170- return try {
171- val result = module.fetchProducts(ProductRequest (skus = skus, type = type) )
186+ try {
187+ val result = module.fetchProducts(request )
172188 when (result) {
173189 is FetchProductsResultProducts -> {
174190 // Merge new products with existing ones
@@ -183,7 +199,7 @@ class OpenIapStore(private val module: OpenIapModule) {
183199 val existingSubIds = _subscriptions .value.map { it.id }.toSet()
184200 val subsToAdd = subs.filter { it.id !in existingSubIds }
185201 _subscriptions .value = _subscriptions .value + subsToAdd
186-
202+
187203 // Also add subscription products to products list
188204 val subProducts = subs
189205 .filterIsInstance<ProductSubscriptionAndroid >()
@@ -202,12 +218,13 @@ class OpenIapStore(private val module: OpenIapModule) {
202218 }
203219 }
204220
221+
205222 // -------------------------------------------------------------------------
206- // Purchases / Restore
223+ // Purchases / Restore - Using GraphQL handler pattern
207224 // -------------------------------------------------------------------------
208- suspend fun getAvailablePurchases ( options : PurchaseOptions ? = null): List < Purchase > {
225+ val getAvailablePurchases: QueryGetAvailablePurchasesHandler = { options ->
209226 setLoading { it.restorePurchases = true }
210- return try {
227+ try {
211228 val result = module.getAvailablePurchases(options)
212229 _availablePurchases .value = result
213230 result
@@ -219,52 +236,46 @@ class OpenIapStore(private val module: OpenIapModule) {
219236 }
220237 }
221238
222- suspend fun restorePurchases (): List <Purchase > = getAvailablePurchases()
223-
224- suspend fun loadPurchases (): List <Purchase > = getAvailablePurchases()
225239
226240 // -------------------------------------------------------------------------
227- // Purchase Flow
241+ // Purchase Flow - Using GraphQL handler pattern
228242 // -------------------------------------------------------------------------
229- suspend fun requestPurchase (
230- skus : List <String >,
231- type : ProductQueryType = ProductQueryType .InApp
232- ): List <Purchase > {
233- val skuForStatus = skus.firstOrNull()
243+ val requestPurchase: MutationRequestPurchaseHandler = { props ->
244+ val skuForStatus = when (val request = props.request) {
245+ is RequestPurchaseProps .Request .Purchase -> request.value.android?.skus?.firstOrNull()
246+ is RequestPurchaseProps .Request .Subscription -> request.value.android?.skus?.firstOrNull()
247+ else -> null
248+ }
249+
234250 if (skuForStatus != null ) {
235251 addPurchasing(skuForStatus)
236252 pendingRequestProductId = skuForStatus
237253 }
238- return try {
239- val request = buildRequestPurchaseProps(skus, type)
240- when (val result = module.requestPurchase(request)) {
241- is RequestPurchaseResultPurchases -> result.value.orEmpty()
242- is RequestPurchaseResultPurchase -> result.value?.let (::listOf).orEmpty()
243- else -> emptyList()
244- }
254+
255+ try {
256+ module.requestPurchase(props)
245257 } finally {
246258 if (skuForStatus != null ) removePurchasing(skuForStatus)
247259 }
248260 }
249261
250- suspend fun finishTransaction (
251- purchase : Purchase ,
252- isConsumable : Boolean = false
253- ): Boolean {
254- val token = purchase.purchaseToken
255- if ((purchase is PurchaseAndroid && purchase.isAcknowledgedAndroid == true ) || (token != null && processedPurchaseTokens.contains(token))) {
256- return true
257- }
258- return try {
259- module.finishTransaction(purchase.toInput(), isConsumable)
260- if (token != null ) processedPurchaseTokens.add(token)
261- true
262- } catch (e: Exception ) {
263- setError(e.message)
264- throw e
262+
263+ // Using GraphQL handler pattern
264+ val finishTransaction: MutationFinishTransactionHandler = { purchaseInput, isConsumable ->
265+ val token = purchaseInput.purchaseToken
266+ // Check if already processed - but we can't check isAcknowledgedAndroid on PurchaseInput
267+ if (token == null || ! processedPurchaseTokens.contains(token)) {
268+ try {
269+ module.finishTransaction(purchaseInput, isConsumable)
270+ if (token != null ) processedPurchaseTokens.add(token)
271+ } catch (e: Exception ) {
272+ setError(e.message)
273+ throw e
274+ }
265275 }
266276 }
267277
278+
268279 // -------------------------------------------------------------------------
269280 // Subscriptions
270281 // -------------------------------------------------------------------------
@@ -352,57 +363,6 @@ class OpenIapStore(private val module: OpenIapModule) {
352363 _status .value = current.copy(loadings = current.loadings.copy(purchasing = set))
353364 }
354365
355- private fun buildRequestPurchaseProps (skus : List <String >, type : ProductQueryType ): RequestPurchaseProps {
356- return when (type) {
357- ProductQueryType .InApp -> {
358- val android = RequestPurchaseAndroidProps (
359- isOfferPersonalized = null ,
360- obfuscatedAccountIdAndroid = null ,
361- obfuscatedProfileIdAndroid = null ,
362- skus = skus
363- )
364- RequestPurchaseProps (
365- request = RequestPurchaseProps .Request .Purchase (
366- RequestPurchasePropsByPlatforms (android = android)
367- ),
368- type = ProductQueryType .InApp
369- )
370- }
371- ProductQueryType .Subs -> {
372- val android = RequestSubscriptionAndroidProps (
373- isOfferPersonalized = null ,
374- obfuscatedAccountIdAndroid = null ,
375- obfuscatedProfileIdAndroid = null ,
376- purchaseTokenAndroid = null ,
377- replacementModeAndroid = null ,
378- skus = skus,
379- subscriptionOffers = null
380- )
381- RequestPurchaseProps (
382- request = RequestPurchaseProps .Request .Subscription (
383- RequestSubscriptionPropsByPlatforms (android = android)
384- ),
385- type = ProductQueryType .Subs
386- )
387- }
388- ProductQueryType .All -> throw IllegalArgumentException (" type must be InApp or Subs when requesting a purchase" )
389- }
390- }
391-
392- private fun Purchase.toInput (): PurchaseInput = when (this ) {
393- is PurchaseAndroid -> PurchaseInput (
394- id = id,
395- ids = ids,
396- isAutoRenewing = isAutoRenewing,
397- platform = platform,
398- productId = productId,
399- purchaseState = purchaseState,
400- purchaseToken = purchaseToken,
401- quantity = quantity,
402- transactionDate = transactionDate
403- )
404- else -> throw UnsupportedOperationException (" Only Android purchases are supported on this platform" )
405- }
406366}
407367
408368// -----------------------------------------------------------------------------
0 commit comments