diff --git a/CHANGELOG.md b/CHANGELOG.md index eca15b1de..eb81113cc 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.14.3 + +### Fixes + +- Fixed computed period prices (`weeklyPrice`, `dailyPrice`, `monthlyPrice`, `yearlyPrice`) displaying incorrectly rounded values on StoreKit 2 in production. For example, a £4.99/week product could show as £5.00/week. This was caused by Apple's `priceFormatStyle` applying storefront-specific rounding to computed values. + ## 4.14.2 ### Enhancements diff --git a/Examples/Advanced/Advanced/Superwall_Advanced-Products.storekit b/Examples/Advanced/Advanced/Superwall_Advanced-Products.storekit index 77764447b..120fe605d 100644 --- a/Examples/Advanced/Advanced/Superwall_Advanced-Products.storekit +++ b/Examples/Advanced/Advanced/Superwall_Advanced-Products.storekit @@ -17,55 +17,51 @@ ], "settings" : { + "_askToBuyEnabled" : false, + "_billingGracePeriodEnabled" : false, + "_billingIssuesEnabled" : false, "_compatibilityTimeRate" : { "3" : 6 }, + "_disableDialogs" : false, "_failTransactionsEnabled" : false, "_locale" : "en_US", + "_renewalBillingIssuesEnabled" : false, "_storefront" : "USA", "_storeKitErrors" : [ { - "current" : null, "enabled" : false, "name" : "Load Products" }, { - "current" : null, "enabled" : false, "name" : "Purchase" }, { - "current" : null, "enabled" : false, "name" : "Verification" }, { - "current" : null, "enabled" : false, "name" : "App Store Sync" }, { - "current" : null, "enabled" : false, "name" : "Subscription Status" }, { - "current" : null, "enabled" : false, "name" : "App Transaction" }, { - "current" : null, "enabled" : false, "name" : "Manage Subscriptions Sheet" }, { - "current" : null, "enabled" : false, "name" : "Refund Request Sheet" }, { - "current" : null, "enabled" : false, "name" : "Offer Code Redeem Sheet" } @@ -91,7 +87,9 @@ "familyShareable" : false, "groupNumber" : 1, "internalID" : "38D8615F", - "introductoryOffer" : null, + "introductoryOffers" : [ + + ], "localizations" : [ { "description" : "Access to diamond features", @@ -119,7 +117,9 @@ "familyShareable" : false, "groupNumber" : 2, "internalID" : "E02B772A", - "introductoryOffer" : null, + "introductoryOffers" : [ + + ], "localizations" : [ { "description" : "Access to pro features", @@ -140,7 +140,7 @@ } ], "version" : { - "major" : 4, + "major" : 5, "minor" : 0 } } diff --git a/Sources/SuperwallKit/Misc/Constants.swift b/Sources/SuperwallKit/Misc/Constants.swift index 41e6bcf49..41dd498ce 100644 --- a/Sources/SuperwallKit/Misc/Constants.swift +++ b/Sources/SuperwallKit/Misc/Constants.swift @@ -18,5 +18,5 @@ let sdkVersion = """ */ let sdkVersion = """ -4.14.2 +4.14.3 """ diff --git a/Sources/SuperwallKit/StoreKit/Products/StoreProduct/SK2StoreProduct.swift b/Sources/SuperwallKit/StoreKit/Products/StoreProduct/SK2StoreProduct.swift index d8e2a4c34..35d12a5db 100644 --- a/Sources/SuperwallKit/StoreKit/Products/StoreProduct/SK2StoreProduct.swift +++ b/Sources/SuperwallKit/StoreKit/Products/StoreProduct/SK2StoreProduct.swift @@ -18,6 +18,7 @@ import StoreKit @available(iOS 15.0, tvOS 15.0, watchOS 8.0, *) struct SK2StoreProduct: StoreProductType { + private let priceFormatterProvider = PriceFormatterProvider() let entitlements: Set init( @@ -61,6 +62,16 @@ struct SK2StoreProduct: StoreProductType { return underlyingSK2Product.price.formatted(underlyingSK2Product.priceFormatStyle) } + /// A `NumberFormatter` for formatting computed prices (daily, weekly, monthly, yearly). + /// Unlike `priceFormatStyle`, this does not apply storefront-specific rounding + /// that can cause values like £4.99 to display as £5.00 in production. + private var priceFormatter: NumberFormatter { + priceFormatterProvider.priceFormatterForSK2( + withCurrencyCode: underlyingSK2Product.priceFormatStyle.currencyCode, + locale: underlyingSK2Product.priceFormatStyle.locale + ) + } + var localizedSubscriptionPeriod: String { guard let subscriptionPeriod = underlyingSK2Product.subscription?.subscriptionPeriod else { return "" @@ -273,7 +284,8 @@ struct SK2StoreProduct: StoreProductType { periods = Decimal(numberOfUnits) } - return (inputPrice / periods).roundedPrice().formatted(underlyingSK2Product.priceFormatStyle) + let result = (inputPrice / periods).roundedPrice() + return priceFormatter.string(from: NSDecimalNumber(decimal: result)) ?? "n/a" } var weeklyPrice: String { @@ -298,7 +310,8 @@ struct SK2StoreProduct: StoreProductType { periods = Decimal(numberOfUnits) } - return (inputPrice / periods).roundedPrice().formatted(underlyingSK2Product.priceFormatStyle) + let result = (inputPrice / periods).roundedPrice() + return priceFormatter.string(from: NSDecimalNumber(decimal: result)) ?? "n/a" } var monthlyPrice: String { @@ -323,7 +336,8 @@ struct SK2StoreProduct: StoreProductType { periods = Decimal(numberOfUnits) } - return (inputPrice / periods).roundedPrice().formatted(underlyingSK2Product.priceFormatStyle) + let result = (inputPrice / periods).roundedPrice() + return priceFormatter.string(from: NSDecimalNumber(decimal: result)) ?? "n/a" } var yearlyPrice: String { @@ -348,7 +362,8 @@ struct SK2StoreProduct: StoreProductType { periods = Decimal(numberOfUnits) } - return (inputPrice / periods).roundedPrice().formatted(underlyingSK2Product.priceFormatStyle) + let result = (inputPrice / periods).roundedPrice() + return priceFormatter.string(from: NSDecimalNumber(decimal: result)) ?? "n/a" } var hasFreeTrial: Bool { @@ -593,15 +608,15 @@ struct SK2StoreProduct: StoreProductType { func trialPeriodPricePerUnit(_ unit: SubscriptionPeriod.Unit) -> String { guard let introductoryDiscount = introductoryDiscount else { - return Decimal(0).formatted(underlyingSK2Product.priceFormatStyle) + return priceFormatter.string(from: 0.00) ?? "n/a" } if introductoryDiscount.price == 0.00 { - return Decimal(0).formatted(underlyingSK2Product.priceFormatStyle) + return priceFormatter.string(from: 0.00) ?? "n/a" } let introMonthlyPrice = introductoryDiscount.pricePerUnit(unit) - return introMonthlyPrice.formatted(underlyingSK2Product.priceFormatStyle) + return priceFormatter.string(from: NSDecimalNumber(decimal: introMonthlyPrice)) ?? "n/a" } var localizedTrialPeriodPrice: String { diff --git a/SuperwallKit.podspec b/SuperwallKit.podspec index 824ebdc63..8650ecd4f 100644 --- a/SuperwallKit.podspec +++ b/SuperwallKit.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = "SuperwallKit" - s.version = "4.14.2" + s.version = "4.14.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"