From d67327c1ef5b4aaaf1f61c086064b21ca21d52d5 Mon Sep 17 00:00:00 2001 From: Jean-Emmanuel BAILLAT Date: Sun, 19 Jul 2026 19:50:57 +0200 Subject: [PATCH] fix: expose monthly equivalent display text --- README.md | 1 + .../KapuschStoreKit2Interop/Interop.swift | 20 ++++++++++++++++++- .../StoreKit2BillingClient.cs | 3 ++- .../StoreKitNativeInterop.iOS.cs | 6 +++++- .../nuget-readme.md | 1 + 5 files changed, 28 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index fe5d81a..2a053b0 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ Public OSS repository that packages a StoreKit 2 iOS interop wrapper into a cons A NuGet package that: - provides a minimal managed API for StoreKit 2 purchase and restore flows; +- exposes store-formatted subscription prices plus an optional annual-to-monthly equivalent formatted with StoreKit's native price style; - redistributes the native wrapper `xcframework` inside the `.nupkg`; - injects the wrapper into consuming apps through `buildTransitive` `NativeReference`. diff --git a/src/Kapusch.StoreKit2ApisForiOSComponents/Native/iOS/KapuschStoreKit2Interop/Sources/KapuschStoreKit2Interop/Interop.swift b/src/Kapusch.StoreKit2ApisForiOSComponents/Native/iOS/KapuschStoreKit2Interop/Sources/KapuschStoreKit2Interop/Interop.swift index 59915d1..a76f9e3 100644 --- a/src/Kapusch.StoreKit2ApisForiOSComponents/Native/iOS/KapuschStoreKit2Interop/Sources/KapuschStoreKit2Interop/Interop.swift +++ b/src/Kapusch.StoreKit2ApisForiOSComponents/Native/iOS/KapuschStoreKit2Interop/Sources/KapuschStoreKit2Interop/Interop.swift @@ -51,6 +51,7 @@ private struct OfferMetadataPayload: Codable { let introOfferDays: Int? let currentPrice: Decimal let currentPriceDisplayText: String + let monthlyEquivalentPriceDisplayText: String? } private final class PurchaseCallbackContext: @unchecked Sendable { @@ -226,6 +227,19 @@ private func totalDays(for offer: Product.SubscriptionOffer?) -> Int? { return unitValue * max(offer.periodCount, 1) } +private func monthlyEquivalentPriceDisplayText( + for product: Product, + subscription: Product.SubscriptionInfo +) -> String? { + guard subscription.subscriptionPeriod.unit == .year, + subscription.subscriptionPeriod.value == 1 else { + return nil + } + + let monthlyPrice = product.price / Decimal(12) + return monthlyPrice.formatted(product.priceFormatStyle) +} + private struct PromotionalOfferInput { let offerId: String let keyId: String @@ -652,7 +666,11 @@ public func kstorekit2_offer_metadata_start( isEligibleForIntroOffer: isEligibleForIntroOffer, introOfferDays: introOfferDays, currentPrice: product.price, - currentPriceDisplayText: product.displayPrice + currentPriceDisplayText: product.displayPrice, + monthlyEquivalentPriceDisplayText: monthlyEquivalentPriceDisplayText( + for: product, + subscription: subscription + ) ) ) } diff --git a/src/Kapusch.StoreKit2ApisForiOSComponents/StoreKit2BillingClient.cs b/src/Kapusch.StoreKit2ApisForiOSComponents/StoreKit2BillingClient.cs index 849458b..3dbc61e 100644 --- a/src/Kapusch.StoreKit2ApisForiOSComponents/StoreKit2BillingClient.cs +++ b/src/Kapusch.StoreKit2ApisForiOSComponents/StoreKit2BillingClient.cs @@ -17,7 +17,8 @@ public sealed record StoreKitOfferMetadata( bool IsEligibleForIntroOffer, int? IntroOfferDays, decimal? CurrentPrice, - string? CurrentPriceDisplayText + string? CurrentPriceDisplayText, + string? MonthlyEquivalentPriceDisplayText = null ); /// diff --git a/src/Kapusch.StoreKit2ApisForiOSComponents/StoreKitNativeInterop.iOS.cs b/src/Kapusch.StoreKit2ApisForiOSComponents/StoreKitNativeInterop.iOS.cs index c82c086..ffdfc04 100644 --- a/src/Kapusch.StoreKit2ApisForiOSComponents/StoreKitNativeInterop.iOS.cs +++ b/src/Kapusch.StoreKit2ApisForiOSComponents/StoreKitNativeInterop.iOS.cs @@ -33,6 +33,9 @@ internal sealed class StoreKitOfferMetadataPayload [JsonPropertyName("currentPriceDisplayText")] public string? CurrentPriceDisplayText { get; init; } + + [JsonPropertyName("monthlyEquivalentPriceDisplayText")] + public string? MonthlyEquivalentPriceDisplayText { get; init; } } internal static unsafe partial class StoreKitNativeInterop @@ -622,7 +625,8 @@ private static IReadOnlyList ParseOfferMetadata(string? p item.IsEligibleForIntroOffer, item.IntroOfferDays, item.CurrentPrice, - item.CurrentPriceDisplayText + item.CurrentPriceDisplayText, + item.MonthlyEquivalentPriceDisplayText )) .ToArray(); } diff --git a/src/Kapusch.StoreKit2ApisForiOSComponents/nuget-readme.md b/src/Kapusch.StoreKit2ApisForiOSComponents/nuget-readme.md index 94e6b31..856f7db 100644 --- a/src/Kapusch.StoreKit2ApisForiOSComponents/nuget-readme.md +++ b/src/Kapusch.StoreKit2ApisForiOSComponents/nuget-readme.md @@ -5,3 +5,4 @@ - Target framework: `net10.0-ios` - Native wrapper: `kstorekit2.xcframework` - Native injection: `buildTransitive` `NativeReference` +- Offer metadata: store-formatted current price and optional monthly equivalent for one-year subscriptions