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:
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user