From f8b7a6bc487f2933749794e7a3a66d82bc98907a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yusuf=20To=CC=88r?= <3296904+yusuftor@users.noreply.github.com> Date: Fri, 15 May 2026 15:17:04 +0200 Subject: [PATCH 1/4] =?UTF-8?q?Make=20day=E2=86=94week=20price=20conversio?= =?UTF-8?q?n=20exact?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- CHANGELOG.md | 6 +++++ Sources/SuperwallKit/Misc/Constants.swift | 2 +- .../StoreProduct/APIStoreProduct.swift | 4 ++-- .../StoreProduct/SK2StoreProduct.swift | 6 +++-- .../StoreProduct/StripeProductType.swift | 6 +++-- .../StoreProduct/SubscriptionPeriod.swift | 9 +++++-- SuperwallKit.podspec | 2 +- .../SubscriptionPeriodPriceTests.swift | 24 +++++++++++++++++++ 8 files changed, 49 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 844598f64b..bd23835281 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ The changelog for `SuperwallKit`. Also see the [releases](https://github.com/superwall/Superwall-iOS/releases) on GitHub. +## 4.15.3 + +### Fixes + +- Fixes computed period prices (`weeklyPrice`, `dailyPrice`) being off by a small amount for products whose subscription period is expressed in days. A 7-day product is exactly one week, but the day↔week conversion went through an approximate `52/365` factor, so a 7-day product priced at e.g. £6.99 could report a `weeklyPrice` of £7.00. Day↔week conversions are now exact. + ## 4.15.2 ### Enhancements diff --git a/Sources/SuperwallKit/Misc/Constants.swift b/Sources/SuperwallKit/Misc/Constants.swift index 9e400afba7..2326830fd3 100644 --- a/Sources/SuperwallKit/Misc/Constants.swift +++ b/Sources/SuperwallKit/Misc/Constants.swift @@ -18,5 +18,5 @@ let sdkVersion = """ */ let sdkVersion = """ -4.15.2 +4.15.3 """ diff --git a/Sources/SuperwallKit/StoreKit/Products/StoreProduct/APIStoreProduct.swift b/Sources/SuperwallKit/StoreKit/Products/StoreProduct/APIStoreProduct.swift index 1fd904b3dc..e8a719cc0d 100644 --- a/Sources/SuperwallKit/StoreKit/Products/StoreProduct/APIStoreProduct.swift +++ b/Sources/SuperwallKit/StoreKit/Products/StoreProduct/APIStoreProduct.swift @@ -194,7 +194,7 @@ struct APIStoreProduct: StoreProductType { let days: Decimal switch unit { case .day: days = Decimal(subscriptionValue) - case .week: days = Decimal(365) / Decimal(52) * Decimal(subscriptionValue) + case .week: days = Decimal(7) * Decimal(subscriptionValue) case .month: days = Decimal(365) / Decimal(12) * Decimal(subscriptionValue) case .year: days = Decimal(365 * subscriptionValue) @unknown default: days = 1 @@ -209,7 +209,7 @@ struct APIStoreProduct: StoreProductType { } let weeks: Decimal switch unit { - case .day: weeks = Decimal(subscriptionValue) * Decimal(52) / Decimal(365) + case .day: weeks = Decimal(subscriptionValue) / Decimal(7) case .week: weeks = Decimal(subscriptionValue) case .month: weeks = Decimal(52) / Decimal(12) * Decimal(subscriptionValue) case .year: weeks = Decimal(52 * subscriptionValue) diff --git a/Sources/SuperwallKit/StoreKit/Products/StoreProduct/SK2StoreProduct.swift b/Sources/SuperwallKit/StoreKit/Products/StoreProduct/SK2StoreProduct.swift index 35d12a5db3..c23844748e 100644 --- a/Sources/SuperwallKit/StoreKit/Products/StoreProduct/SK2StoreProduct.swift +++ b/Sources/SuperwallKit/StoreKit/Products/StoreProduct/SK2StoreProduct.swift @@ -277,7 +277,8 @@ struct SK2StoreProduct: StoreProductType { case .month: periods = Decimal(365) / Decimal(12) * Decimal(numberOfUnits) case .week: - periods = Decimal(365) / Decimal(52) * Decimal(numberOfUnits) + // 7 days per week exactly — not 365/52. + periods = Decimal(7) * Decimal(numberOfUnits) case .day: periods = Decimal(numberOfUnits) @unknown default: @@ -305,7 +306,8 @@ struct SK2StoreProduct: StoreProductType { case .week: periods = Decimal(numberOfUnits) case .day: - periods = Decimal(numberOfUnits) * Decimal(52) / Decimal(365) + // 7 days per week exactly — a 7-day product is 1 week. + periods = Decimal(numberOfUnits) / Decimal(7) @unknown default: periods = Decimal(numberOfUnits) } diff --git a/Sources/SuperwallKit/StoreKit/Products/StoreProduct/StripeProductType.swift b/Sources/SuperwallKit/StoreKit/Products/StoreProduct/StripeProductType.swift index 7016d6af38..709ac3380b 100644 --- a/Sources/SuperwallKit/StoreKit/Products/StoreProduct/StripeProductType.swift +++ b/Sources/SuperwallKit/StoreKit/Products/StoreProduct/StripeProductType.swift @@ -299,7 +299,8 @@ struct StripeProductType: StoreProductType { } if subscriptionPeriod.unit == .week { - periods = Decimal(365) / Decimal(52) * Decimal(numberOfUnits) + // 7 days per week exactly — not 365/52. + periods = Decimal(7) * Decimal(numberOfUnits) } if subscriptionPeriod.unit == .day { @@ -338,7 +339,8 @@ struct StripeProductType: StoreProductType { } if subscriptionPeriod.unit == .day { - periods = Decimal(numberOfUnits) * Decimal(52) / Decimal(365) + // 7 days per week exactly — a 7-day product is 1 week. + periods = Decimal(numberOfUnits) / Decimal(7) } let rounded = (price / periods).roundedPrice() diff --git a/Sources/SuperwallKit/StoreKit/Products/StoreProduct/SubscriptionPeriod.swift b/Sources/SuperwallKit/StoreKit/Products/StoreProduct/SubscriptionPeriod.swift index 6503e84be5..280b5e06f8 100644 --- a/Sources/SuperwallKit/StoreKit/Products/StoreProduct/SubscriptionPeriod.swift +++ b/Sources/SuperwallKit/StoreKit/Products/StoreProduct/SubscriptionPeriod.swift @@ -131,7 +131,10 @@ extension SubscriptionPeriod { let periodsPerDay: Decimal = { switch self.unit { case .day: return 1 - case .week: return Decimal(365) / Decimal(52) + // A week is exactly 7 days. Don't route through 52/365 — 52 weeks is + // only 364 days, so that approximation makes a 1-week product's daily + // price disagree with an equivalent 7-day product's. + case .week: return 7 case .month: return Decimal(365) / Decimal(12) case .year: return 365 } @@ -145,7 +148,9 @@ extension SubscriptionPeriod { func pricePerWeek(withTotalPrice price: Decimal) -> Decimal { let periodsPerWeek: Decimal = { switch self.unit { - case .day: return Decimal(52) / Decimal(365) + // A day is exactly 1/7 of a week. The old 52/365 made a 7-day product + // resolve to 0.997 weeks, inflating its weekly price (e.g. 6.99 → 7.00). + case .day: return Decimal(1) / Decimal(7) case .week: return 1 case .month: return Decimal(52) / Decimal(12) case .year: return 52 diff --git a/SuperwallKit.podspec b/SuperwallKit.podspec index cdf48a52df..d7ef7686ee 100644 --- a/SuperwallKit.podspec +++ b/SuperwallKit.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = "SuperwallKit" - s.version = "4.15.2" + s.version = "4.15.3" s.summary = "Superwall: In-App Paywalls Made Easy" s.description = "Paywall infrastructure for mobile apps :) we make things like editing your paywall and running price tests as easy as clicking a few buttons. superwall.com" diff --git a/Tests/SuperwallKitTests/StoreKit/Products/StoreProduct/SubscriptionPeriodPriceTests.swift b/Tests/SuperwallKitTests/StoreKit/Products/StoreProduct/SubscriptionPeriodPriceTests.swift index 6dfadf1281..53b8ae2f25 100644 --- a/Tests/SuperwallKitTests/StoreKit/Products/StoreProduct/SubscriptionPeriodPriceTests.swift +++ b/Tests/SuperwallKitTests/StoreKit/Products/StoreProduct/SubscriptionPeriodPriceTests.swift @@ -119,6 +119,30 @@ struct SubscriptionPeriodPriceTests { #expect(weeklyPrice == Decimal(string: "4.99")) } + @Test("Weekly price for a 7-day subscription equals the price") + func testWeeklyPriceForSevenDaySubscription() { + // A 7-day period IS one week, so the weekly price must equal the price. + // Before the day↔week conversion fix this divided by (7 × 52/365) ≈ 0.9973, + // giving 6.99 / 0.9973 ≈ 7.009, which truncated to "7.00" — a penny too high. + let period = SubscriptionPeriod(value: 7, unit: .day) + let weeklyPrice = period.pricePerWeek(withTotalPrice: Decimal(string: "6.99")!) + + #expect(weeklyPrice == Decimal(string: "6.99")) + } + + @Test("7-day and 1-week subscriptions produce the same weekly price") + func testSevenDayAndOneWeekWeeklyPriceAreConsistent() { + // The two are the same duration — their derived weekly price must match. + let price = Decimal(string: "6.99")! + let sevenDay = SubscriptionPeriod(value: 7, unit: .day) + let oneWeek = SubscriptionPeriod(value: 1, unit: .week) + + #expect( + sevenDay.pricePerWeek(withTotalPrice: price) + == oneWeek.pricePerWeek(withTotalPrice: price) + ) + } + // MARK: - Monthly Price Tests @Test("Monthly price for yearly subscription") From 539755e558357cd5446dc923f53584f505bfa661 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yusuf=20To=CC=88r?= <3296904+yusuftor@users.noreply.github.com> Date: Fri, 15 May 2026 15:19:42 +0200 Subject: [PATCH 2/4] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bd23835281..1d7029334f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ The changelog for `SuperwallKit`. Also see the [releases](https://github.com/sup ### Fixes -- Fixes computed period prices (`weeklyPrice`, `dailyPrice`) being off by a small amount for products whose subscription period is expressed in days. A 7-day product is exactly one week, but the day↔week conversion went through an approximate `52/365` factor, so a 7-day product priced at e.g. £6.99 could report a `weeklyPrice` of £7.00. Day↔week conversions are now exact. +- Fixes computed period prices (`weeklyPrice`, `dailyPrice`) being off by a small amount for products whose subscription period is expressed in days. ## 4.15.2 From 59d045c6353ef2f8546ee265356cea18960088cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yusuf=20To=CC=88r?= <3296904+yusuftor@users.noreply.github.com> Date: Fri, 15 May 2026 15:19:53 +0200 Subject: [PATCH 3/4] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d7029334f..6dda353213 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,7 @@ The changelog for `SuperwallKit`. Also see the [releases](https://github.com/sup ### Fixes -- Changes the Superscript spm package repo source to a new lightweight repo meaning that the download of the package is way faster. +- Changes the Superscript SPM package repo source to a new lightweight repo meaning that the download of the package is way faster. ## 4.15.1 From 9b1635c56a95902cf5b469740549c170cc762186 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yusuf=20To=CC=88r?= <3296904+yusuftor@users.noreply.github.com> Date: Fri, 15 May 2026 15:39:17 +0200 Subject: [PATCH 4/4] Route SK2 computed prices through the normalized period MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- .../StoreProduct/SK2StoreProduct.swift | 108 +++--------------- .../StoreProduct/SubscriptionPeriod.swift | 2 +- .../SubscriptionPeriodPriceTests.swift | 65 +++++++++++ 3 files changed, 83 insertions(+), 92 deletions(-) diff --git a/Sources/SuperwallKit/StoreKit/Products/StoreProduct/SK2StoreProduct.swift b/Sources/SuperwallKit/StoreKit/Products/StoreProduct/SK2StoreProduct.swift index c23844748e..f87c2bbc07 100644 --- a/Sources/SuperwallKit/StoreKit/Products/StoreProduct/SK2StoreProduct.swift +++ b/Sources/SuperwallKit/StoreKit/Products/StoreProduct/SK2StoreProduct.swift @@ -263,108 +263,34 @@ struct SK2StoreProduct: StoreProductType { } var dailyPrice: String { - guard let subscriptionPeriod = underlyingSK2Product.subscription?.subscriptionPeriod else { - return "n/a" - } - - let numberOfUnits = subscriptionPeriod.value - var periods: Decimal = 1.0 - let inputPrice = underlyingSK2Product.price - - switch subscriptionPeriod.unit { - case .year: - periods = Decimal(365 * numberOfUnits) - case .month: - periods = Decimal(365) / Decimal(12) * Decimal(numberOfUnits) - case .week: - // 7 days per week exactly — not 365/52. - periods = Decimal(7) * Decimal(numberOfUnits) - case .day: - periods = Decimal(numberOfUnits) - @unknown default: - periods = Decimal(numberOfUnits) - } - - let result = (inputPrice / periods).roundedPrice() - return priceFormatter.string(from: NSDecimalNumber(decimal: result)) ?? "n/a" + return formattedComputedPrice { $0.pricePerDay(withTotalPrice: $1) } } var weeklyPrice: String { - guard let subscriptionPeriod = underlyingSK2Product.subscription?.subscriptionPeriod else { - return "n/a" - } - - let numberOfUnits = subscriptionPeriod.value - var periods: Decimal = 1.0 - let inputPrice = underlyingSK2Product.price - - switch subscriptionPeriod.unit { - case .year: - periods = Decimal(52 * numberOfUnits) - case .month: - periods = Decimal(52) / Decimal(12) * Decimal(numberOfUnits) - case .week: - periods = Decimal(numberOfUnits) - case .day: - // 7 days per week exactly — a 7-day product is 1 week. - periods = Decimal(numberOfUnits) / Decimal(7) - @unknown default: - periods = Decimal(numberOfUnits) - } - - let result = (inputPrice / periods).roundedPrice() - return priceFormatter.string(from: NSDecimalNumber(decimal: result)) ?? "n/a" + return formattedComputedPrice { $0.pricePerWeek(withTotalPrice: $1) } } var monthlyPrice: String { - guard let subscriptionPeriod = underlyingSK2Product.subscription?.subscriptionPeriod else { - return "n/a" - } - - let numberOfUnits = subscriptionPeriod.value - var periods: Decimal = 1.0 - let inputPrice = underlyingSK2Product.price - - switch subscriptionPeriod.unit { - case .year: - periods = Decimal(12 * numberOfUnits) - case .month: - periods = Decimal(1 * numberOfUnits) - case .week: - periods = Decimal(numberOfUnits) * Decimal(12) / Decimal(52) - case .day: - periods = Decimal(numberOfUnits) * Decimal(12) / Decimal(365) - @unknown default: - periods = Decimal(numberOfUnits) - } - - let result = (inputPrice / periods).roundedPrice() - return priceFormatter.string(from: NSDecimalNumber(decimal: result)) ?? "n/a" + return formattedComputedPrice { $0.pricePerMonth(withTotalPrice: $1) } } var yearlyPrice: String { - guard let subscriptionPeriod = underlyingSK2Product.subscription?.subscriptionPeriod else { + return formattedComputedPrice { $0.pricePerYear(withTotalPrice: $1) } + } + + /// Formats a per-period price derived from the *normalized* subscription + /// period. `subscriptionPeriod` is built via `SubscriptionPeriod.from(...)`, + /// which applies `normalized()` — collapsing StoreKit's occasional `.day × 7` + /// into `.week × 1`. Without that, a weekly product reported by StoreKit in + /// days would divide by an approximate day↔week factor and report a + /// `weeklyPrice` a penny off (e.g. £6.99 → £7.00). + private func formattedComputedPrice( + _ perPeriod: (SubscriptionPeriod, Decimal) -> Decimal + ) -> String { + guard let subscriptionPeriod = subscriptionPeriod else { return "n/a" } - - let numberOfUnits = subscriptionPeriod.value - var periods: Decimal = 1.0 - let inputPrice = underlyingSK2Product.price - - switch subscriptionPeriod.unit { - case .year: - periods = Decimal(numberOfUnits) - case .month: - periods = Decimal(numberOfUnits) / Decimal(12) - case .week: - periods = Decimal(numberOfUnits) / Decimal(52) - case .day: - periods = Decimal(numberOfUnits) / Decimal(365) - @unknown default: - periods = Decimal(numberOfUnits) - } - - let result = (inputPrice / periods).roundedPrice() + let result = perPeriod(subscriptionPeriod, underlyingSK2Product.price) return priceFormatter.string(from: NSDecimalNumber(decimal: result)) ?? "n/a" } diff --git a/Sources/SuperwallKit/StoreKit/Products/StoreProduct/SubscriptionPeriod.swift b/Sources/SuperwallKit/StoreKit/Products/StoreProduct/SubscriptionPeriod.swift index 280b5e06f8..21c54aa638 100644 --- a/Sources/SuperwallKit/StoreKit/Products/StoreProduct/SubscriptionPeriod.swift +++ b/Sources/SuperwallKit/StoreKit/Products/StoreProduct/SubscriptionPeriod.swift @@ -107,7 +107,7 @@ public final class SubscriptionPeriod: NSObject, Sendable { /// Occasionally, StoreKit seems to send back a value 7 days for a 7day trial /// instead of a value of 1 week for a trial of 7 days in length. /// Source: https://github.com/RevenueCat/react-native-purchases/issues/348 - private func normalized() -> SubscriptionPeriod { + func normalized() -> SubscriptionPeriod { switch unit { case .day: if value.isMultiple(of: 7) { diff --git a/Tests/SuperwallKitTests/StoreKit/Products/StoreProduct/SubscriptionPeriodPriceTests.swift b/Tests/SuperwallKitTests/StoreKit/Products/StoreProduct/SubscriptionPeriodPriceTests.swift index 53b8ae2f25..d4bd7e9e33 100644 --- a/Tests/SuperwallKitTests/StoreKit/Products/StoreProduct/SubscriptionPeriodPriceTests.swift +++ b/Tests/SuperwallKitTests/StoreKit/Products/StoreProduct/SubscriptionPeriodPriceTests.swift @@ -57,6 +57,29 @@ struct SubscriptionPeriodPriceTests { #expect(dailyPrice == Decimal(string: "0.99")) } + @Test("Daily price for a 1-week subscription divides by exactly 7") + func testDailyPriceForWeeklySubscriptionDividesBySeven() { + // A week is exactly 7 days. Before the day↔week fix this divided by + // 365/52 ≈ 7.019, giving 7.00 / 7.019 ≈ 0.9973 → "0.99" — a penny too low. + let period = SubscriptionPeriod(value: 1, unit: .week) + let dailyPrice = period.pricePerDay(withTotalPrice: Decimal(string: "7.00")!) + + #expect(dailyPrice == Decimal(string: "1.00")) + } + + @Test("7-day and 1-week subscriptions produce the same daily price") + func testSevenDayAndOneWeekDailyPriceAreConsistent() { + // The two are the same duration — their derived daily price must match. + let price = Decimal(string: "7.00")! + let sevenDay = SubscriptionPeriod(value: 7, unit: .day) + let oneWeek = SubscriptionPeriod(value: 1, unit: .week) + + #expect( + sevenDay.pricePerDay(withTotalPrice: price) + == oneWeek.pricePerDay(withTotalPrice: price) + ) + } + @Test("Daily price for multi-month subscription") func testDailyPriceForThreeMonthSubscription() { // $24.99/3 months should be ~$0.27/day (24.99 / 90) @@ -246,4 +269,46 @@ struct SubscriptionPeriodPriceTests { // 0.99 / 365 = 0.00271... rounds down to 0.00 #expect(dailyPrice == Decimal(string: "0.00")) } + + // MARK: - Normalization + + // StoreKit sometimes reports a weekly subscription as `.day × 7` rather than + // `.week × 1`. `normalized()` collapses day/month multiples so the computed + // price logic (and `SK2StoreProduct`, which routes through it) treats a + // 7-day product as exactly one week — otherwise its weekly price came out a + // penny high (e.g. £6.99 → £7.00). + + @Test("A 7-day period normalizes to 1 week") + func testSevenDayPeriodNormalizesToOneWeek() { + let normalized = SubscriptionPeriod(value: 7, unit: .day).normalized() + + #expect(normalized.value == 1) + #expect(normalized.unit == .week) + } + + @Test("A 14-day period normalizes to 2 weeks") + func testFourteenDayPeriodNormalizesToTwoWeeks() { + let normalized = SubscriptionPeriod(value: 14, unit: .day).normalized() + + #expect(normalized.value == 2) + #expect(normalized.unit == .week) + } + + @Test("A non-7-multiple day period stays in days") + func testThreeDayPeriodStaysInDays() { + let normalized = SubscriptionPeriod(value: 3, unit: .day).normalized() + + #expect(normalized.value == 3) + #expect(normalized.unit == .day) + } + + @Test("Normalized 7-day period yields a weekly price equal to the price") + func testNormalizedSevenDayWeeklyPriceEqualsPrice() { + // The full chain: a 7-day period normalizes to 1 week, and the weekly + // price of a 1-week product is the price itself. + let normalized = SubscriptionPeriod(value: 7, unit: .day).normalized() + let weeklyPrice = normalized.pricePerWeek(withTotalPrice: Decimal(string: "6.99")!) + + #expect(weeklyPrice == Decimal(string: "6.99")) + } }