Skip to content

Commit 5516697

Browse files
author
Claude Bot
committed
fix(billing): mark Freedom purchased BEFORE acknowledgePurchase async call
THE ACTUAL BUG (post-purchase flow): When the user completes the Freedom purchase in the Play Billing sheet: 1. purchasesUpdatedListener fires → handlePurchase() is called 2. OLD CODE: markAsFreedomPurchased() was called only INSIDE the acknowledgePurchase() async callback — i.e. after Google's server round-trip (can take 1-3+ seconds) 3. MEANWHILE: onResume() fires as the app returns from the billing sheet → loadWebViewContent() reloads the WebView → queryActiveSubscriptions() runs concurrently 4. The freshly loaded WebView calls isFreedomPurchased() at DOMContentLoaded → SharedPrefs still says FALSE (markAsFreedomPurchased not called yet) → shows BUY BUTTONS instead of the Freedom confirmation card 5. queryActiveSubscriptions() may also return an empty list at this point → clears the mark (before today's 5s-delay fix) or does nothing wrong 6. Only AFTER acknowledgePurchase completes does markAsFreedomPurchased() run — but the WebView has already rendered the wrong state and the JS callback either landed on the wrong page state or got lost FIX: Move markAsFreedomPurchased(), markAsPurchased(), updateTrialState() and evaluateWebViewJsWithRetry(true) to BEFORE the isAcknowledged check, so SharedPreferences is updated synchronously the instant purchaseState == PURCHASED is confirmed. The async acknowledgePurchase() only handles the server-side ack step now; it no longer gates any UI update.
1 parent 822e3f5 commit 5516697

1 file changed

Lines changed: 11 additions & 9 deletions

File tree

app/src/main/kotlin/com/google/ai/sample/MainActivity.kt

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1255,6 +1255,14 @@ class MainActivity : ComponentActivity() {
12551255
Log.d(TAG, "handlePurchase: Purchase state is PURCHASED.")
12561256
if (MainActivityBillingStateEvaluator.containsSubscriptionProduct(purchase, freedomProductId)) {
12571257
Log.d(TAG, "handlePurchase: Purchase contains Freedom product ID: $freedomProductId")
1258+
// Mark as purchased IMMEDIATELY (before acknowledgement) so that any concurrent
1259+
// queryActiveSubscriptions() or WebView reload already sees the correct state.
1260+
// purchaseState == PURCHASED already confirms the payment; acknowledgement is
1261+
// just a server-side confirmation step that must not gate the UI update.
1262+
TrialManager.markAsFreedomPurchased(this)
1263+
TrialManager.markAsPurchased(this)
1264+
updateTrialState(TrialManager.getTrialState(this, null))
1265+
evaluateWebViewJsWithRetry("window.onFreedomPurchaseStateChanged && window.onFreedomPurchaseStateChanged(true)")
12581266
if (!purchase.isAcknowledged) {
12591267
Log.i(TAG, "handlePurchase: Purchase not acknowledged. Acknowledging now.")
12601268
val acknowledgePurchaseParams = AcknowledgePurchaseParams.newBuilder()
@@ -1265,11 +1273,6 @@ class MainActivity : ComponentActivity() {
12651273
if (ackBillingResult.responseCode == BillingClient.BillingResponseCode.OK) {
12661274
Log.i(TAG, "Freedom subscription purchase acknowledged successfully.")
12671275
updateStatusMessage("Thank you for your Freedom subscription!")
1268-
TrialManager.markAsFreedomPurchased(this)
1269-
TrialManager.markAsPurchased(this)
1270-
updateTrialState(TrialManager.getTrialState(this, null))
1271-
evaluateWebViewJsWithRetry("window.onFreedomPurchaseStateChanged && window.onFreedomPurchaseStateChanged(true)")
1272-
Log.d(TAG, "handlePurchase Freedom: Stopping TrialTimerService.")
12731276
val stopIntent = Intent(this, TrialTimerService::class.java)
12741277
stopIntent.action = TrialTimerService.ACTION_STOP_TIMER
12751278
startService(stopIntent)
@@ -1281,10 +1284,9 @@ class MainActivity : ComponentActivity() {
12811284
} else {
12821285
Log.i(TAG, "handlePurchase: Freedom subscription already acknowledged.")
12831286
updateStatusMessage("Freedom subscription already active.")
1284-
TrialManager.markAsFreedomPurchased(this)
1285-
TrialManager.markAsPurchased(this)
1286-
updateTrialState(TrialManager.getTrialState(this, null))
1287-
evaluateWebViewJsWithRetry("window.onFreedomPurchaseStateChanged && window.onFreedomPurchaseStateChanged(true)")
1287+
val stopIntent = Intent(this, TrialTimerService::class.java)
1288+
stopIntent.action = TrialTimerService.ACTION_STOP_TIMER
1289+
startService(stopIntent)
12881290
}
12891291
} else if (MainActivityBillingStateEvaluator.containsSubscriptionProduct(purchase, subscriptionProductId)) {
12901292
Log.d(TAG, "handlePurchase: Purchase contains target product ID: $subscriptionProductId")

0 commit comments

Comments
 (0)