Fix mixed currencies in paywall price variables (PW-133)#3119
Conversation
a55d650 to
8ecc985
Compare
📸 Snapshot Test137 modified, 445 unchanged
🛸 Powered by Emerge Tools |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #3119 +/- ##
=======================================
Coverage 79.47% 79.47%
=======================================
Files 357 357
Lines 14351 14351
Branches 1960 1960
=======================================
Hits 11406 11406
Misses 2141 2141
Partials 804 804 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
tonidero
left a comment
There was a problem hiding this comment.
Just a question on why we added decimals to round numbers in some currencies... Feels we should leave that as is if possible.
On Android, different paywall price variables showed different currencies for the same user. This was caused by `product.offer_price` and `product.secondary_offer_price` using the pre-formatted `Price.formatted` string (which may have been created with a different locale) instead of formatting with the `currencyLocale` parameter. Changes: - Add `Price.getFormatted(locale)` function that formats prices consistently using the provided locale - Update `productOfferPrice()` in VariableProcessorV2 to accept a locale parameter and use `price.getFormatted(locale)` instead of `price.formatted` - This ensures `product.offer_price` and `product.secondary_offer_price` use the same locale as `product.currency_symbol` and other price variables Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Extend the PW-133 fix to all code paths that used price.formatted instead of getFormatted(locale): VariableDataProvider per-period helpers, intro offer prices, PriceExtensions.localized(), and offer price per-period in VariableProcessorV2. Update test expectations accordingly. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…rice functions - Remove duplicate Price.getFormatted(locale) overload that caused a compile error - Drop explicit currencySymbol override that produced full-width ¥ for JPY - Add locale param to resolveOfferPrice, productOfferPrice, productOfferPricePerPeriod in PricingPhaseExtensions so offer prices use currencyLocale consistently - Pass currencyLocale at both resolveOfferPrice call sites in VariableProcessorV2 - Update JPY test expectations (¥ → ¥) and NL storefront USD expectation Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
USD $1,000 now formats as $1,000.00 with getFormatted(locale) instead of using price.formatted (which omits trailing zeros). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Import BigDecimal and RoundingMode instead of using fully-qualified names - Update getFormatted to preserve decimal precision from Price.formatted using regex detection, so "$1,000" stays "$1,000" rather than becoming "$1,000.00" - Strengthen OfferPriceLocaleTests assertions from startsWith/contains to isEqualTo - Update test expectations in VariableProcessorTest and PackageInfoForTest to match the preserved store precision for lifetime packages Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces the brittle regex-based decimal detection with a simpler approach: set minimumFractionDigits=0 so round numbers (e.g. $1.00) display without trailing zeros ($1), while non-round prices keep their cents ($1.99). Also removes the redundant amountMicros==0L early-return for intro offer prices — getFormatted already handles zero amounts correctly. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
6f98a25 to
faced9b
Compare
PaywallDialog.kt and PaywallViewModel.kt had debug Logger.d calls and handleCloseRequest changes that are unrelated to the PW-133 price locale fix. Revert both to main's current state. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
$1.30 should stay $1.30, not become $1.3. Only whole-unit prices (amountMicros % 1_000_000 == 0) get minimumFractionDigits=0; everything else uses the currency's default fraction digits. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove the whole-unit trailing zero detection (amountMicros % 1_000_000 == 0) from getFormatted(). The fix for PW-133 is using the correct locale — let NumberFormat decide how many decimal places to show, same as Google does. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
223b9c9 to
30ac6ac
Compare
…es-show-mixed-currencies
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
tonidero
left a comment
There was a problem hiding this comment.
Looks good! Thanks for bearing with all the iterations 🙏
**This is an automatic release.** ## RevenueCat SDK ### ✨ New Features * Use Amazon deep link for Amazon subscription management (#3291) via Cesar de la Vega (@vegaro) * Introduce purchases codegen package (#3163) via Jaewoong Eum (@skydoves) ### 🐞 Bugfixes * Fix Test Store Purchase dialog not cancelling purchase on outside tap (#3289) via Antonio Pallares (@ajpallares) ## RevenueCatUI SDK ### Paywallv2 #### 🐞 Bugfixes * Fix mixed currencies in paywall price variables (PW-133) (#3119) via Facundo Menzella (@facumenzella) ### 🔄 Other Changes * Add docs for the codegen plugin (#3288) via Jaewoong Eum (@skydoves) * Run integration tests against all backend environments (#3278) via Toni Rico (@tonidero) * Use merge queue for release PR merging (#3281) via Antonio Pallares (@ajpallares) * ci: warn when pre-built material-icons are imported in :ui:revenuecatui (#3282) via Facundo Menzella (@facumenzella) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Low risk: this is a version/release bookkeeping update plus docs/CI deployment path changes, with no functional runtime logic changes beyond the reported version string. > > **Overview** > Cuts the `9.29.0` release by switching all references from `9.29.0-SNAPSHOT` to `9.29.0` (Gradle `VERSION_NAME`, internal `frameworkVersion`, and sample/test app dependency versions). > > Updates documentation publishing to point to the `9.29.0` docs directory (CircleCI S3 sync path and `docs/index.html` redirect), and rolls forward `CHANGELOG.md`/`CHANGELOG.latest.md` with the 9.29.0 release notes. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit bf217fd. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->

Summary
Fixes https://linear.app/revenuecat/issue/PW-133/android-paywall-price-variables-show-mixed-currencies
On Android, different paywall price variables showed mixed currencies/locales for the same user. The root cause was that
product.offer_priceandproduct.secondary_offer_priceused the pre-formattedPrice.formattedstring — which was created with whatever locale the store SDK happened to use — instead of formatting with the correctcurrencyLocale.Fix
Use
NumberFormat.getCurrencyInstance(locale)with the correct locale, and let it do its thing.Price.getFormatted(locale)that re-formats prices from rawamountMicrosusing the provided locale and BigDecimal arithmetic (to avoid floating-point precision issues)currencyLocalethroughVariableProcessorV2andPricingPhaseExtensionsso all offer price variables (product.offer_price,product.secondary_offer_price, per-period variants) use the same locale asproduct.currency_symbolprice.formattedcall sites inVariableDataProviderto useprice.getFormatted(locale)Root Cause
product.currency_symbolwas correctly usingcurrencyLocale:But
product.offer_pricewas using the pre-formatted price:The locale controls symbol position, decimal separator, thousands separator, and decimal places — same as how Google formats prices in the Play Store.
Test plan
PriceLocaleTeststo verifyPrice.getFormatted()formats correctly with different localesOfferPriceLocaleTeststo verify offer price variables usecurrencyLocale🤖 Generated with Claude Code
Note
Medium Risk
Changes how paywall price/offer variables are formatted across locales and currencies, which is user-visible and could affect displayed amounts/symbols if formatting differs from previous
Price.formattedstrings. Core purchase logic is untouched, but broad test expectation updates indicate wide surface area.Overview
Fixes mixed-locale currency output in paywall variables by reformatting prices from
amountMicrosusing the providedcurrencyLocalerather than relying onPrice.formatted.Adds
Price.getFormatted(locale)(BigDecimal-based, currency-fraction-digit aware) and threads locale throughPricingPhaseoffer-price helpers andVariableProcessorV2soproduct.offer_price,product.secondary_offer_price, and per-period offer variants use consistent locale formatting.Updates multiple tests and golden strings to match locale-correct currency symbol placement/separators, and adds focused coverage in
PriceLocaleTestsandOfferPriceLocaleTests.Written by Cursor Bugbot for commit ce48d16. This will update automatically on new commits. Configure here.