@@ -169,19 +169,46 @@ class OpenIapModule(private val context: Context) : OpenIapProtocol, PurchasesUp
169169 fun launchBillingWith (detailsList : List <ProductDetails >) {
170170 // Build params for all requested SKUs in order
171171 val paramsList = mutableListOf<BillingFlowParams .ProductDetailsParams >()
172- for (pd in detailsList) {
172+ val requestedOffersBySku = mutableMapOf<String , MutableList <String >>()
173+ if (type == ProductRequest .ProductRequestType .Subs ) {
174+ request.subscriptionOffers.forEach { offer ->
175+ if (offer.offerToken.isNotEmpty()) {
176+ val queue = requestedOffersBySku.getOrPut(offer.sku) { mutableListOf () }
177+ queue.add(offer.offerToken)
178+ }
179+ }
180+ }
181+
182+ for ((index, pd) in detailsList.withIndex()) {
173183 val builder = BillingFlowParams .ProductDetailsParams .newBuilder()
174184 .setProductDetails(pd)
175185 if (type == ProductRequest .ProductRequestType .Subs ) {
176- val offerToken = pd.subscriptionOfferDetails?.firstOrNull()?.offerToken
177- if (offerToken.isNullOrEmpty()) {
178- Log .w(TAG , " No subscription offer available for ${pd.productId} " )
186+ val availableOfferTokens = pd.subscriptionOfferDetails?.map { it.offerToken } ? : emptyList()
187+ val requestedTokenFromQueue = requestedOffersBySku[pd.productId]?.let { queue ->
188+ if (queue.isNotEmpty()) queue.removeAt(0 ) else null
189+ }
190+ val requestedTokenFromIndex = request.subscriptionOffers.getOrNull(index)?.takeIf { it.sku == pd.productId }?.offerToken
191+ val resolvedOfferToken = requestedTokenFromQueue
192+ ? : requestedTokenFromIndex
193+ ? : pd.subscriptionOfferDetails?.firstOrNull()?.offerToken
194+
195+ if (resolvedOfferToken.isNullOrEmpty()) {
196+ OpenIapLog .w(" No subscription offer available for ${pd.productId} " , TAG )
179197 val err = OpenIapError .SkuOfferMismatch
180198 purchaseErrorListeners.forEach { runCatching { it.onPurchaseError(err) } }
181199 currentPurchaseCallback?.invoke(Result .success(emptyList()))
182200 return
183201 }
184- builder.setOfferToken(offerToken)
202+
203+ if (availableOfferTokens.isNotEmpty() && ! availableOfferTokens.contains(resolvedOfferToken)) {
204+ OpenIapLog .w(" Requested offerToken=$resolvedOfferToken not found for ${pd.productId} " , TAG )
205+ val err = OpenIapError .SkuOfferMismatch
206+ purchaseErrorListeners.forEach { runCatching { it.onPurchaseError(err) } }
207+ currentPurchaseCallback?.invoke(Result .success(emptyList()))
208+ return
209+ }
210+
211+ builder.setOfferToken(resolvedOfferToken)
185212 }
186213 paramsList.add(builder.build())
187214 }
@@ -244,7 +271,7 @@ class OpenIapModule(private val context: Context) : OpenIapProtocol, PurchasesUp
244271 launchBillingWith(ordered)
245272 // Do not complete here; wait for onPurchasesUpdated
246273 } else {
247- Log .w(TAG , " queryProductDetails failed: code=${billingResult.responseCode} msg=${billingResult.debugMessage} " )
274+ OpenIapLog .w(" queryProductDetails failed: code=${billingResult.responseCode} msg=${billingResult.debugMessage} " , TAG )
248275 val err = OpenIapError .QueryProduct ()
249276 purchaseErrorListeners.forEach { runCatching { it.onPurchaseError(err) } }
250277 currentPurchaseCallback?.invoke(Result .success(emptyList()))
@@ -397,7 +424,7 @@ class OpenIapModule(private val context: Context) : OpenIapProtocol, PurchasesUp
397424 billingResult.responseCode,
398425 billingResult.debugMessage
399426 )
400- Log .w(TAG , " Purchase failed: code=${billingResult.responseCode} msg=${error.message} " )
427+ OpenIapLog .w(" Purchase failed: code=${billingResult.responseCode} msg=${error.message} " , TAG )
401428 // Surface framework-specific error upstream (maintains type for UserCancelled, etc.)
402429 purchaseErrorListeners.forEach { listener ->
403430 runCatching { listener.onPurchaseError(error) }
@@ -569,9 +596,9 @@ class OpenIapModule(private val context: Context) : OpenIapProtocol, PurchasesUp
569596 object : BillingClientStateListener {
570597 override fun onBillingSetupFinished (billingResult : BillingResult ) {
571598 if (billingResult.responseCode != BillingClient .BillingResponseCode .OK ) {
572- Log .w(
573- TAG ,
599+ OpenIapLog .w(
574600 " Billing setup finished with error: ${billingResult.debugMessage} " ,
601+ TAG ,
575602 )
576603 onFailure(IllegalStateException (billingResult.debugMessage ? : " Billing setup failed" ))
577604 return
0 commit comments