1.1.1 (46): subscription IAP, export compliance, metadata

- Switch premium to AutoRenewable subscription (com.mealmood.premium.monthly.sub);
  legacy one-time purchasers retain lifetime access via StoreManager fallback
- Add ITSAppUsesNonExemptEncryption=false to Info.plist (no custom encryption)
- Update storekit config with both legacy NonConsumable and new subscription
- Bump version 1.1.1 build 46; update fastlane metadata and release notes (6 langs)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
alexandrev-tibco
2026-05-18 11:28:14 +02:00
parent 7da7995538
commit 89b35dc4ae
20 changed files with 74 additions and 86 deletions
+16 -2
View File
@@ -14,7 +14,21 @@
],
"products" : [
{
"displayPrice" : "2.99",
"familyShareable" : false,
"internalID" : "B2F4D1A0-3E7C-4F8B-9D2E-1A5C6B0E3F7D",
"localizations" : [
{
"description" : "Lifetime access to all premium features.",
"displayName" : "MealMood Premium (Legacy)",
"locale" : "en_US"
}
],
"productID" : "com.mealmood.premium.monthly",
"referenceName" : "MealMood Premium Legacy",
"type" : "NonConsumable"
}
],
"settings" : {
"_applicationInternalID" : "2147483647",
@@ -60,7 +74,7 @@
"locale" : "en_US"
}
],
"productID" : "com.mealmood.premium.monthly",
"productID" : "com.mealmood.premium.monthly.sub",
"recurringSubscriptionPeriod" : "P1M",
"referenceName" : "MealMood Premium Monthly",
"subscriptionPricePointID" : "0"
+4 -2
View File
@@ -15,11 +15,13 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0.6</string>
<string>1.1.1</string>
<key>CFBundleVersion</key>
<string>41</string>
<string>46</string>
<key>GADApplicationIdentifier</key>
<string>ca-app-pub-1549720748100858~9985112590</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSCalendarsFullAccessUsageDescription</key>
+20 -25
View File
@@ -27,11 +27,13 @@ final class StoreManager: ObservableObject {
@Published var debugLoadedProductIds: [String] = []
@Published var debugLoadedProducts: [String] = []
static let monthlyProductId = "com.mealmood.premium.monthly"
static let monthlyProductId = "com.mealmood.premium.monthly.sub"
static let legacyOneTimeProductId = "com.mealmood.premium.monthly"
private var productIds: [String] {
var ids = [Self.monthlyProductId]
var ids = [Self.monthlyProductId, Self.legacyOneTimeProductId]
if let bundleId = Bundle.main.bundleIdentifier {
ids.append("\(bundleId).premium.monthly.sub")
ids.append("\(bundleId).premium.monthly")
}
@@ -115,42 +117,35 @@ final class StoreManager: ObservableObject {
}
var monthlyProduct: Product? {
if let match = products.first(where: { product in
product.id == Self.monthlyProductId && product.type == .autoRenewable
}) {
if let match = products.first(where: { $0.id == Self.monthlyProductId && $0.type == .autoRenewable }) {
return match
}
if let match = products.first(where: { $0.id == Self.monthlyProductId }) {
return match
}
if let match = products.first(where: { $0.id.contains("monthly") && $0.type == .autoRenewable }) {
return match
}
if let match = products.first(where: { $0.type == .autoRenewable }) {
return match
}
if let match = products.first(where: { $0.id == Self.legacyOneTimeProductId }) {
return match
}
return products.first
}
var debugProductIds: [String] { productIds }
static func hasActiveSubscription() async -> Bool {
// Check all known product ID variants to handle both the canonical ID
// and any bundle-prefixed IDs that may have been used at purchase time.
var knownIds: [String] = [monthlyProductId]
if let bundleId = Bundle.main.bundleIdentifier {
knownIds.append("\(bundleId).premium.monthly")
}
let ids = Set(knownIds)
let bundleId = Bundle.main.bundleIdentifier ?? ""
let subscriptionIds: Set<String> = [monthlyProductId, "\(bundleId).premium.monthly.sub"]
// Legacy one-time purchases grant lifetime access no expiration check needed.
let legacyIds: Set<String> = [legacyOneTimeProductId, "\(bundleId).premium.monthly"]
for await result in Transaction.currentEntitlements {
if case .verified(let transaction) = result,
ids.contains(transaction.productID),
transaction.revocationDate == nil {
guard case .verified(let transaction) = result,
transaction.revocationDate == nil else { continue }
if legacyIds.contains(transaction.productID) {
return true
}
if subscriptionIds.contains(transaction.productID) {
if let expirationDate = transaction.expirationDate, expirationDate < Date() {
continue
}