Skip to content

Fix iOS purchase receipt loss after backend acknowledgment failure#8817

Open
atavism wants to merge 3 commits into
mainfrom
atavism/ios-pending-purchase
Open

Fix iOS purchase receipt loss after backend acknowledgment failure#8817
atavism wants to merge 3 commits into
mainfrom
atavism/ios-pending-purchase

Conversation

@atavism
Copy link
Copy Markdown
Contributor

@atavism atavism commented May 28, 2026

Partially resolves https://github.com/getlantern/engineering/issues/3557

  • Keep iOS store transactions pending until Lantern backend receipt acknowledgment succeeds
  • Retry failed purchase acknowledgments with short backoff instead of completing the transaction immediately
  • Persist selected plan metadata so retried purchase updates still send the intended plan ID
  • Trust only fresh backend user state before treating a purchase as already active
  • Register LanternPlatformService before AppPurchase init so startup purchase updates can safely call backend APIs

Copilot AI review requested due to automatic review settings May 28, 2026 21:01
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves iOS in-app purchase reliability by preventing StoreKit transactions from being completed until the Lantern backend successfully acknowledges the receipt, reducing the chance of “lost” receipts after transient backend/network failures.

Changes:

  • Registers LanternPlatformService earlier so startup purchase updates can safely call backend APIs.
  • Persists selected plan metadata for pending purchases so retries can send the intended planId.
  • Adds acknowledgement retry logic (short backoff) and keeps transactions pending on ack failure; relies on fresh backend user state before treating purchases as already active.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
lib/core/services/injection_container.dart Registers LanternPlatformService before AppPurchase init to support early purchase update handling.
lib/core/services/app_purchase.dart Adds pending plan persistence + ack retry flow; avoids completing iOS transactions until backend ack succeeds.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +724 to +737
if (attempt >= _ackRetryDelays.length) {
appLogger.warning(
'[AppPurchase] Acknowledgment retries exhausted; StoreKit transaction '
'remains pending: productID=${purchaseDetails.productID} '
'purchaseID=${purchaseDetails.purchaseID}',
);
return;
}

final delay = _ackRetryDelays[attempt];
_ackRetryAttempts[key] = attempt + 1;
appLogger.info(
'[AppPurchase] Scheduling acknowledgment retry ${attempt + 1}/'
'${_ackRetryDelays.length} in $delay: '
Comment on lines +733 to +739
final delay = _ackRetryDelays[attempt];
_ackRetryAttempts[key] = attempt + 1;
appLogger.info(
'[AppPurchase] Scheduling acknowledgment retry ${attempt + 1}/'
'${_ackRetryDelays.length} in $delay: '
'productID=${purchaseDetails.productID} purchaseID=${purchaseDetails.purchaseID}',
);
Comment on lines +862 to +871
try {
final decoded = jsonDecode(raw);
if (decoded is Map) {
return decoded.map(
(key, value) => MapEntry(key.toString(), value.toString()),
);
}
} catch (e, st) {
appLogger.error('Failed to parse pending purchase plans', e, st);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@atavism, let's not merge this yet. Let's test this thoroughly because at the moment, our Apple payment is stable.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants