Skip to content

Fix day↔week price conversion (6.99 weekly shows as 7.00)#473

Merged
yusuftor merged 7 commits into
developfrom
fix-day-week-price-conversion
May 15, 2026
Merged

Fix day↔week price conversion (6.99 weekly shows as 7.00)#473
yusuftor merged 7 commits into
developfrom
fix-day-week-price-conversion

Conversation

@yusuftor
Copy link
Copy Markdown
Collaborator

@yusuftor yusuftor commented May 15, 2026

Summary

A customer reported a 7-day £6.99 product showing weeklyPrice as £7.00 on the live paywall, while the StoreKit purchase sheet (and the paywall editor) correctly showed £6.99.

Root cause

Two compounding issues:

  1. Approximate day↔week conversion. The computed-price logic converted days↔weeks via a 52/365 factor. But 52 weeks = 364 days, not 365 — so a 7-day period resolved to ~0.997 weeks instead of exactly 1.
  2. SK2StoreProduct bypassed normalization. StoreKit sometimes reports a weekly subscription as .day × 7 rather than .week × 1. The SDK has SubscriptionPeriod.normalized() to collapse that to .week × 1, but SK2StoreProduct's price getters switched directly on the raw StoreKit period and never called it — so a weekly product hit the day↔week branch and 6.99 / 0.997 ≈ 7.009 truncated to 7.00.

Changes

  • Exact day↔week conversion (7 days = 1 week) in all computed-price paths: the SubscriptionPeriod helper and the inline switches in SK2StoreProduct, APIStoreProduct, StripeProductType.
  • SK2StoreProduct now routes through the normalized period. The four price getters (dailyPrice/weeklyPrice/monthlyPrice/yearlyPrice) were replaced with calls through the existing normalized subscriptionPeriod property and its pricePerDay/Week/Month/Year helpers. A StoreKit .day × 7 now collapses to .week × 1 before the math runs, so a weekly product's weekly price equals its price. This also removes the duplicated per-getter conversion logic.
  • Month↔year was already exact (12); week↔month / week↔year stay as conventional approximations — those aren't whole-number relationships.
  • SubscriptionPeriod.normalized() made internal for testability.
  • Version bumped to 4.15.3 (Constants.swift, podspec, CHANGELOG).

Tests

SubscriptionPeriodPriceTests — 28 tests, incl. new coverage:

  • weeklyPrice / dailyPrice for a 7-day period (pre-fix produced the penny-off value).
  • 7-day vs 1-week consistency for both weekly and daily prices.
  • Normalization: 7-day → 1 week, 14-day → 2 weeks, 3-day stays in days, and the full chain (7-day normalized → weeklyPrice equals the price).

SK2StoreProduct's getters can't be unit-tested directly (StoreKit.Product isn't constructible in tests); the normalization + helper tests cover the logic they now route through.

Test plan

  • All unit tests pass
  • Build passes
  • swiftlint clean

Follow-up

APIStoreProduct and StripeProductType still carry their own inline conversion switches (now arithmetically correct). Consolidating all four product types onto the SubscriptionPeriod helper 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 weeklyPrice as £7.00. The root causes were an imprecise 52/365 day↔week conversion factor and SK2StoreProduct's price getters bypassing the existing normalized() function that collapses StoreKit's occasional .day × 7 encoding into .week × 1.

  • Arithmetic fix: 52/365 replaced with exact 7 / 1/7 in SubscriptionPeriod, APIStoreProduct, and StripeProductType.
  • Normalization fix: SK2StoreProduct's four inline price getters replaced with a single formattedComputedPrice helper routing through the normalized subscriptionPeriod property.
  • Testability: normalized() promoted from private to internal for 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

Filename Overview
Sources/SuperwallKit/StoreKit/Products/StoreProduct/SubscriptionPeriod.swift Core fix: .week periodsPerDay changed from 365/52 to exact 7; .day periodsPerWeek changed from 52/365 to exact 1/7. normalized() promoted to internal for test access.
Sources/SuperwallKit/StoreKit/Products/StoreProduct/SK2StoreProduct.swift Replaced four duplicated per-getter price switch statements with a single formattedComputedPrice helper routing through the normalized subscriptionPeriod property.
Sources/SuperwallKit/StoreKit/Products/StoreProduct/APIStoreProduct.swift Inline day-week conversion constants corrected from 365/52 and 52/365 to exact 7 and 1/7.
Sources/SuperwallKit/StoreKit/Products/StoreProduct/StripeProductType.swift Same arithmetic correction as APIStoreProduct for daily/weekly price inline switches.
Tests/SuperwallKitTests/StoreKit/Products/StoreProduct/SubscriptionPeriodPriceTests.swift New tests cover: weekly price equality for 7-day period, 7-day/1-week consistency, daily price for 1-week period, and the full normalization chain.
CHANGELOG.md 4.15.3 entry added with accurate description of the computed price fix.

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 --> ROUND
Loading

Reviews (2): Last reviewed commit: "Route SK2 computed prices through the no..." | Re-trigger Greptile

yusuftor and others added 5 commits April 28, 2026 14:43
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>
yusuftor and others added 2 commits May 15, 2026 15:35
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>
@yusuftor
Copy link
Copy Markdown
Collaborator Author

@greptileai

@yusuftor yusuftor merged commit a12a3c4 into develop May 15, 2026
4 checks passed
@yusuftor yusuftor deleted the fix-day-week-price-conversion branch May 15, 2026 15:12
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.

1 participant