Skip to content

Fix addSuccessfullyPostedToken for new purchases in PostPendingTransactionsHelper#3239

Merged
facumenzella merged 9 commits into
mainfrom
facundo/fix-auto-renewing-token-caching
Mar 16, 2026
Merged

Fix addSuccessfullyPostedToken for new purchases in PostPendingTransactionsHelper#3239
facumenzella merged 9 commits into
mainfrom
facundo/fix-auto-renewing-token-caching

Conversation

@facumenzella

@facumenzella facumenzella commented Mar 16, 2026

Copy link
Copy Markdown
Member

Summary

#3198 added an unconditional addSuccessfullyPostedToken call in PostPendingTransactionsHelper.transactionPostSuccess to persist the isAutoRenewing status for auto-renewing change detection. However, this call was redundant — billing.consumeAndSave already calls addSuccessfullyPostedToken(token, isAutoRenewing) on the success path before transactionPostSuccess fires. The extra call caused two issues:

1. Integration test failures

The tests entersOfflineEntitlementsModeIfCachedCustomerInfoAndPostingPendingPurchasesReturns500 and postsPurchasePerformedOnFallbackURLWhenRecoveringToMainServer use a relaxed mock for BillingAbstract where consumeAndSave is a no-op. The unconditional addSuccessfullyPostedToken prematurely marked tokens as posted during SDK init, so subsequent syncs found no pending purchases and couldn't enter offline entitlements mode.

2. Production retry behavior change

Tokens that received a 500 error (handled via offline entitlements) were marked as posted and never retried on the next sync. This means the backend could permanently miss a purchase.

Fix

Removed the addSuccessfullyPostedToken call from transactionPostSuccess entirely. Token caching (including isAutoRenewing) is already handled by billing.consumeAndSave on the success path. On the error path (500 + offline entitlements), consumeAndSave is intentionally not called, so the token stays uncached for retry on the next sync. All auto-renewing change detection functionality from #3198 is preserved.

Test plan

  • Existing PostPendingTransactionsHelperTest auto-renewing tests pass
  • New test: synced transactions do not call addSuccessfullyPostedToken directly
  • DeviceCacheTest passes
  • entersOfflineEntitlementsModeIfCachedCustomerInfoAndPostingPendingPurchasesReturns500 passes
  • postsPurchasePerformedOnFallbackURLWhenRecoveringToMainServer passes

🤖 Generated with Claude Code


Note

Medium Risk
Touches purchase sync caching behavior by removing an eager addSuccessfullyPostedToken call; incorrect behavior could affect retry/sync reliability, but change is small and covered by updated tests.

Overview
Removes the deviceCache.addSuccessfullyPostedToken side effect from PostPendingTransactionsHelper’s transactionPostSuccess, relying on billing.consumeAndSave to persist posted tokens and isAutoRenewing metadata.

Clarifies the intent of saveAutoRenewingStatus for legacy-migrated tokens and updates tests to assert addSuccessfullyPostedToken is not called directly for either auto-renewing-change resyncs or newly-synced purchases.

Written by Cursor Bugbot for commit b38bab1. This will update automatically on new commits. Configure here.

…PendingTransactionsHelper

PR #3198 added an unconditional addSuccessfullyPostedToken call in
transactionPostSuccess to persist isAutoRenewing status for auto-renewing
change detection. However, this also fired for new purchases, which:

1. Broke integration tests that use a relaxed mock for BillingAbstract
   (consumeAndSave is a no-op), prematurely marking tokens as posted
   during SDK init so later syncs couldn't find pending purchases.
2. Changed production retry behavior: tokens that got a 500 (handled via
   offline entitlements) were marked as posted and never retried.

Now addSuccessfullyPostedToken only fires for tokens reposted due to an
auto-renewing status change. New purchases rely on billing.consumeAndSave
to cache the token, as before #3198.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@facumenzella facumenzella added the pr:fix A bug fix label Mar 16, 2026
@facumenzella facumenzella requested a review from tonidero March 16, 2026 08:56
@facumenzella

Copy link
Copy Markdown
Member Author

@RCGitBot please test

facumenzella and others added 2 commits March 16, 2026 10:03
Triggers run-firebase-tests-purchases-integration-tests (production)
to validate the fix for the two failing integration tests.

This commit should be removed before merging.
Adds a `run-firebase-tests` pipeline parameter that can be toggled
from the CircleCI UI to run all Firebase integration test jobs on
any branch, without requiring a release/* branch.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@codecov

codecov Bot commented Mar 16, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 79.40%. Comparing base (7eceaca) to head (b38bab1).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3239      +/-   ##
==========================================
- Coverage   79.40%   79.40%   -0.01%     
==========================================
  Files         356      356              
  Lines       14348    14345       -3     
  Branches     1959     1959              
==========================================
- Hits        11393    11390       -3     
  Misses       2151     2151              
  Partials      804      804              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

transaction.purchaseToken,
transaction.isAutoRenewing,
)
}

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.

So I missed this in the previous PR... but I'm wondering now whether this is needed at all... As in, normally this path would already go through the PostReceiptHelper which adds the posted token to the cache. The exceptions are if the type of purchase is pending or unknown... (Since there is a safeguard before that path gets called) but in those scenarios we wouldn't want to add the token as posted... So I actually think we need to remove this entirely? Lmk if I missed something though 🙏

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

You're right — removed it entirely in e1397bb. billing.consumeAndSave already calls addSuccessfullyPostedToken(token, purchase.isAutoRenewing) on the success path before transactionPostSuccess fires, so the call here was always redundant. And on the error path (500 + offline entitlements), consumeAndSave is intentionally not called, so the token stays uncached for retry.

billing.consumeAndSave already calls addSuccessfullyPostedToken (with
isAutoRenewing) on the success path, before transactionPostSuccess fires.
The call here was redundant for all cases — new purchases and
auto-renewing changes alike.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@facumenzella facumenzella marked this pull request as ready for review March 16, 2026 11:34
@facumenzella facumenzella requested a review from a team as a code owner March 16, 2026 11:34
@facumenzella facumenzella requested a review from tonidero March 16, 2026 11:34
Comment thread .circleci/config.yml Outdated

@tonidero tonidero left a comment

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.

Just reverting CI changes and I think it looks good.

Comment thread .circleci/config.yml Outdated
Comment thread .circleci/config.yml Outdated
Since #3198 hasn't been released, no users have tokens migrated from
the legacy StringSet cache with isAutoRenewing = null. The call to
saveAutoRenewingStatus(unchangedTokens) was only useful for that
migration case — for tokens already cached with isAutoRenewing set,
it's a no-op by definition (unchanged means same value).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

@cursor cursor Bot left a comment

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.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

There are 3 total unresolved issues (including 1 from previous review).

Fix All in Cursor

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

The legacy StringSet cache predates #3198 — users upgrading from any
older SDK version will have tokens with isAutoRenewing = null. This
call populates that value on the first sync so auto-renewing change
detection works for pre-existing subscriptions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@facumenzella

Copy link
Copy Markdown
Member Author

@tonidero tests passed. I'll revert the temp thing in the CI

@tonidero tonidero left a comment

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.

🚢

@facumenzella facumenzella enabled auto-merge March 16, 2026 12:36
@facumenzella facumenzella added this pull request to the merge queue Mar 16, 2026
Merged via the queue into main with commit 840ce39 Mar 16, 2026
30 checks passed
@facumenzella facumenzella deleted the facundo/fix-auto-renewing-token-caching branch March 16, 2026 13:10
github-merge-queue Bot pushed a commit that referenced this pull request Mar 16, 2026
**This is an automatic release.**

## RevenueCat SDK
### 🐞 Bugfixes
* Fix & Standardize Galaxy Date Parsing Edge Cases (#3216) via Will
Taylor (@fire-at-will)
* Fix addSuccessfullyPostedToken for new purchases in
PostPendingTransactionsHelper (#3239) via Facundo Menzella
(@facumenzella)
* [Galaxy]: Fix race condition when fetching Galaxy products (#3213) via
Will Taylor (@fire-at-will)
* Fixes double padding in PaywallActivity on Android 15+ when
`edgeToEdge` parameter is false (#3227) via Cesar de la Vega (@vegaro)

## RevenueCatUI SDK
### 🐞 Bugfixes
* Fix bold text not rendering in Markdown lists (#3228) via Cesar de la
Vega (@vegaro)
* Fix: Clear in-memory offerings cache on locale override to prevent
stale paywall data (#3225) via Antonio Pallares (@ajpallares)
### Paywallv2
#### ✨ New Features
* Feature: Update default paywall (#3133) via Jacob Rakidzich
(@JZDesign)
#### 🐞 Bugfixes
* Fix V2 paywall safe area in landscape mode (#3221) via Cesar de la
Vega (@vegaro)

### 🔄 Other Changes
* Run integration tests on all branches (#3242) via Toni Rico
(@tonidero)
* Migrate Firebase Test Lab jobs to CircleCI emulators (#3238) via Toni
Rico (@tonidero)
* Run metalava on galaxy module in test-galaxy job (#3235) via Will
Taylor (@fire-at-will)
* Add offering_id to custom paywall impression event (#3230) via Rick
(@rickvdl)
* Cache isAutoRenewing to detect subscription changes without
syncPurchases (#3198) via Facundo Menzella (@facumenzella)
* Bump fastlane-plugin-revenuecat_internal from `e146447` to `3e8c384`
(#3233) via dependabot[bot] (@dependabot[bot])

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Low risk release housekeeping: version string bumps and
documentation/deployment path updates with no functional runtime logic
changes beyond the exposed version constant.
> 
> **Overview**
> Publishes the `9.26.0` release by removing `-SNAPSHOT` across
build/version metadata (root `VERSION_NAME`, `.version`,
`Config.frameworkVersion`, and sample/test app dependency pins).
> 
> Updates release documentation artifacts by adding the `9.26.0` notes
to `CHANGELOG.md`/`CHANGELOG.latest.md`, switching docs deployment in
CircleCI to sync `docs/9.26.0` to S3, and updating `docs/index.html` to
redirect to the new version.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
0a30a45. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr:fix A bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants