Skip to content

Commit cf26e31

Browse files
Fix iOS 26.4+ restore: defer handleRestore to allow pending transactions to arrive (#286)
* Initial plan * Fix iOS restore: defer handleRestore to allow pending transactions to arrive On iOS 26.4+, restoreCompletedTransactionsFinished may be called before all restored transactions are delivered via updatedTransactions. This caused handleRestore to be called with an empty array. Fix by deferring the handleRestore delivery using a 500ms timer, giving pending updatedTransactions callbacks time to process restored transactions first. Agent-Logs-Url: https://github.com/libgdx/gdx-pay/sessions/9dc875b1-3666-46a7-a485-0946d118e71b Co-authored-by: keesvandieren <863966+keesvandieren@users.noreply.github.com> * Add rationale comment for 500ms timer delay Agent-Logs-Url: https://github.com/libgdx/gdx-pay/sessions/9dc875b1-3666-46a7-a485-0946d118e71b Co-authored-by: keesvandieren <863966+keesvandieren@users.noreply.github.com> * Switch from NSTimer to libGDX Timer for restore deferral Replaces the iOS-specific NSTimer (which didn't compile) with com.badlogic.gdx.utils.Timer, matching the approach suggested in issue #285. Agent-Logs-Url: https://github.com/libgdx/gdx-pay/sessions/042d60fa-727f-43f6-bef1-8e8002016b3b Co-authored-by: keesvandieren <863966+keesvandieren@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: keesvandieren <863966+keesvandieren@users.noreply.github.com>
1 parent 3092154 commit cf26e31

1 file changed

Lines changed: 28 additions & 3 deletions

File tree

gdx-pay-iosrobovm-apple/src/main/java/com/badlogic/gdx/pay/ios/apple/PurchaseManageriOSApple.java

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import com.badlogic.gdx.math.MathUtils;
2020
import com.badlogic.gdx.pay.*;
21+
import com.badlogic.gdx.utils.Timer;
2122
import libcore.io.Base64;
2223
import org.robovm.apple.foundation.*;
2324
import org.robovm.apple.storekit.*;
@@ -52,6 +53,7 @@ public class PurchaseManageriOSApple implements PurchaseManager {
5253
private NSArray<SKProduct> products;
5354

5455
private final List<Transaction> restoredTransactions = new ArrayList<Transaction>();
56+
private Timer.Task restoreTimerTask;
5557

5658
@Override
5759
public String storeName () {
@@ -107,6 +109,9 @@ public boolean installed () {
107109
@Override
108110
public void dispose () {
109111
if (appleObserver != null) {
112+
// Cancel any pending restore delivery timer.
113+
cancelRestoreTimer();
114+
110115
// Remove and null our apple transaction observer.
111116

112117
SKPaymentQueue defaultQueue = SKPaymentQueue.getDefaultQueue();
@@ -151,6 +156,8 @@ public void purchase (String identifier) {
151156
public void purchaseRestore () {
152157
log(LOGTYPELOG, "Restoring purchases...");
153158

159+
// Cancel any pending restore delivery timer.
160+
cancelRestoreTimer();
154161
// Clear previously restored transactions.
155162
restoredTransactions.clear();
156163
// Start the restore flow.
@@ -483,16 +490,27 @@ else if (error.getCode() == SKErrorCode.PaymentCancelled.value()) {
483490

484491
@Override
485492
public void restoreCompletedTransactionsFinished (SKPaymentQueue queue) {
486-
// All products have been restored.
487493
log(LOGTYPELOG, "All transactions have been restored!");
488494

489-
observer.handleRestore(restoredTransactions.toArray(new Transaction[restoredTransactions.size()]));
490-
restoredTransactions.clear();
495+
// On iOS 26.4+, restoreCompletedTransactionsFinished may be called
496+
// before all restored transactions are delivered via updatedTransactions.
497+
// Defer delivery to allow any pending restored transactions to be processed.
498+
// 500ms is chosen to safely exceed the observed ~60ms gap between callbacks.
499+
cancelRestoreTimer();
500+
restoreTimerTask = Timer.schedule(new Timer.Task() {
501+
@Override
502+
public void run() {
503+
restoreTimerTask = null;
504+
observer.handleRestore(restoredTransactions.toArray(new Transaction[restoredTransactions.size()]));
505+
restoredTransactions.clear();
506+
}
507+
}, 0.5f);
491508
}
492509

493510
@Override
494511
public void restoreCompletedTransactionsFailed (SKPaymentQueue queue, NSError error) {
495512
// Restoration failed.
513+
cancelRestoreTimer();
496514

497515
// Decide if user cancelled or transaction failed.
498516
if (error.getCode() == SKErrorCode.PaymentCancelled.value()) {
@@ -505,6 +523,13 @@ public void restoreCompletedTransactionsFailed (SKPaymentQueue queue, NSError er
505523
}
506524
}
507525

526+
private void cancelRestoreTimer() {
527+
if (restoreTimerTask != null) {
528+
restoreTimerTask.cancel();
529+
restoreTimerTask = null;
530+
}
531+
}
532+
508533
void log (final int type, final String message) {
509534
log(type, message, null);
510535
}

0 commit comments

Comments
 (0)