Fix day↔week price conversion (6.99 weekly shows as 7.00)#473
Merged
Conversation
A 7-day subscription period is exactly one week, but the day↔week conversions used an approximate 52/365 weeks-per-day factor (52 weeks is only 364 days). For a product the SDK sees as `.day` × 7, this made `weeklyPrice` divide by 7 × 52/365 ≈ 0.9973 instead of 1, inflating the result — e.g. a £6.99/7-day product reported a `weeklyPrice` of £7.00 while StoreKit's own purchase sheet (correctly) showed £6.99. Replace the day↔week factor with the exact ratio (7 days = 1 week) in all four computed-price paths — the `SubscriptionPeriod` helper (SK1) and the inline switches in `SK2StoreProduct`, `APIStoreProduct`, and `StripeProductType`. Month↔year was already exact (12); week/month and week/year conversions remain conventional approximations since those relationships genuinely aren't whole-number. Adds a regression test: a 7-day period at 6.99 yields a `weeklyPrice` of 6.99 (pre-fix it produced 7.00), plus a consistency check that 7-day and 1-week periods yield the same weekly price. Bumps version to 4.15.3. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…-conversion # Conflicts: # CHANGELOG.md
SK2StoreProduct's dailyPrice/weeklyPrice/monthlyPrice/yearlyPrice switched directly on the raw StoreKit `Product.SubscriptionPeriod`, bypassing `SubscriptionPeriod.normalized()`. StoreKit can report a weekly subscription as `.day × 7` rather than `.week × 1`, so those getters hit the day↔week conversion branch and a £6.99 weekly product reported a `weeklyPrice` of £7.00 — even though `SK2StoreProduct` already had a `subscriptionPeriod` property that builds the normalized SDK type. Replace the four inline switches with calls through that normalized `subscriptionPeriod` and its `pricePerDay/Week/Month/Year` helpers. A StoreKit `.day × 7` now collapses to `.week × 1` before the price math, so a weekly product's weekly price equals its price. Also removes the per-getter duplicated conversion logic. Make `SubscriptionPeriod.normalized()` internal and add tests: 7-day → 1 week, 14-day → 2 weeks, 3-day stays in days, and the full chain (7-day normalized → weeklyPrice equals the price). Adds the missing pricePerDay coverage for the day↔week fix as well. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Collaborator
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A customer reported a 7-day £6.99 product showing
weeklyPriceas £7.00 on the live paywall, while the StoreKit purchase sheet (and the paywall editor) correctly showed £6.99.Root cause
Two compounding issues:
52/365factor. But 52 weeks = 364 days, not 365 — so a 7-day period resolved to ~0.997 weeks instead of exactly 1.SK2StoreProductbypassed normalization. StoreKit sometimes reports a weekly subscription as.day × 7rather than.week × 1. The SDK hasSubscriptionPeriod.normalized()to collapse that to.week × 1, butSK2StoreProduct's price getters switched directly on the raw StoreKit period and never called it — so a weekly product hit the day↔week branch and6.99 / 0.997 ≈ 7.009truncated to7.00.Changes
SubscriptionPeriodhelper and the inline switches inSK2StoreProduct,APIStoreProduct,StripeProductType.SK2StoreProductnow routes through the normalized period. The four price getters (dailyPrice/weeklyPrice/monthlyPrice/yearlyPrice) were replaced with calls through the existing normalizedsubscriptionPeriodproperty and itspricePerDay/Week/Month/Yearhelpers. A StoreKit.day × 7now collapses to.week × 1before the math runs, so a weekly product's weekly price equals its price. This also removes the duplicated per-getter conversion logic.SubscriptionPeriod.normalized()made internal for testability.Tests
SubscriptionPeriodPriceTests— 28 tests, incl. new coverage:weeklyPrice/dailyPricefor a 7-day period (pre-fix produced the penny-off value).weeklyPriceequals the price).SK2StoreProduct's getters can't be unit-tested directly (StoreKit.Productisn't constructible in tests); the normalization + helper tests cover the logic they now route through.Test plan
Follow-up
APIStoreProductandStripeProductTypestill carry their own inline conversion switches (now arithmetically correct). Consolidating all four product types onto theSubscriptionPeriodhelper as the single source of truth would prevent this class of bug recurring per-file — worth a separate PR.🤖 Generated with Claude Code
Greptile Summary
This PR fixes a penny-off price display bug where a 7-day £6.99 subscription showed
weeklyPriceas £7.00. The root causes were an imprecise52/365day↔week conversion factor andSK2StoreProduct's price getters bypassing the existingnormalized()function that collapses StoreKit's occasional.day × 7encoding into.week × 1.52/365replaced with exact7/1/7inSubscriptionPeriod,APIStoreProduct, andStripeProductType.SK2StoreProduct's four inline price getters replaced with a singleformattedComputedPricehelper routing through the normalizedsubscriptionPeriodproperty.normalized()promoted fromprivatetointernalfor direct test access.Confidence Score: 5/5
The change is safe to merge — it makes two well-scoped arithmetic corrections directly verified by the new tests.
Both fixes are narrow, independently testable, and the new test suite exercises the exact scenario that produced the original bug. The SK2StoreProduct refactor removes duplication rather than adding new logic paths.
No files require special attention. APIStoreProduct and StripeProductType still carry inline switches but are arithmetically correct after this PR.
Important Files Changed
.weekperiodsPerDay changed from365/52to exact7;.dayperiodsPerWeek changed from52/365to exact1/7.normalized()promoted tointernalfor test access.formattedComputedPricehelper routing through the normalizedsubscriptionPeriodproperty.365/52and52/365to exact7and1/7.Flowchart
%%{init: {'theme': 'neutral'}}%% flowchart TD SK2[SK2StoreProduct] --> FCP[formattedComputedPrice helper] FCP --> SP[subscriptionPeriod property] SP --> NRM[normalized: day x7 to week x1] NRM --> HELPER[pricePerDay / pricePerWeek / pricePerMonth / pricePerYear] HELPER --> MATH_W[.week: periodsPerDay = 7 x value EXACT] HELPER --> MATH_D[.day: periodsPerWeek = value / 7 EXACT] MATH_W --> ROUND[NSDecimalNumber rounding down scale 2] MATH_D --> ROUND ROUND --> OUT[Formatted price string] API[APIStoreProduct and StripeProductType] --> INLINE[Inline switches day-week now exact 7] INLINE --> ROUNDReviews (2): Last reviewed commit: "Route SK2 computed prices through the no..." | Re-trigger Greptile