Incorrect handling of receipt purchases (#439)
This commit is contained in:
parent
1551b59f21
commit
4c4876c5f7
|
@ -32,7 +32,7 @@
|
|||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2.2.1</string>
|
||||
<string>2.3.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>3548</string>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2.2.1</string>
|
||||
<string>2.3.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>3548</string>
|
||||
<key>LSBackgroundOnly</key>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2.2.1</string>
|
||||
<string>2.3.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>3548</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
<key>CFBundlePackageType</key>
|
||||
<string>XPC!</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2.2.1</string>
|
||||
<string>2.3.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>3548</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
//
|
||||
// LocalProduct+InApp.swift
|
||||
// Passepartout
|
||||
//
|
||||
// Created by Davide De Rosa on 12/21/23.
|
||||
// Copyright (c) 2023 Davide De Rosa. All rights reserved.
|
||||
//
|
||||
// https://github.com/passepartoutvpn
|
||||
//
|
||||
// This file is part of Passepartout.
|
||||
//
|
||||
// Passepartout is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Passepartout is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import PassepartoutCore
|
||||
|
||||
public protocol LocalInApp: InAppProtocol where ProductIdentifier == LocalProduct {
|
||||
}
|
||||
|
||||
extension StoreKitInApp: LocalInApp where ProductIdentifier == LocalProduct {
|
||||
}
|
|
@ -28,12 +28,6 @@ import Foundation
|
|||
import PassepartoutCore
|
||||
import PassepartoutProviders
|
||||
|
||||
public protocol LocalInApp: InAppProtocol where ProductIdentifier == LocalProduct {
|
||||
}
|
||||
|
||||
extension StoreKitInApp: LocalInApp where ProductIdentifier == LocalProduct {
|
||||
}
|
||||
|
||||
@MainActor
|
||||
public final class ProductManager: NSObject, ObservableObject {
|
||||
private let inApp: any LocalInApp
|
||||
|
@ -106,6 +100,8 @@ public final class ProductManager: NSObject, ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: Main interface
|
||||
|
||||
public func canMakePayments() -> Bool {
|
||||
inApp.canMakePurchases()
|
||||
}
|
||||
|
@ -180,25 +176,19 @@ public final class ProductManager: NSObject, ObservableObject {
|
|||
public func restorePurchases() async throws {
|
||||
try await inApp.restorePurchases()
|
||||
}
|
||||
|
||||
public func hasPurchased(_ product: LocalProduct) -> Bool {
|
||||
isActivePurchase(product)
|
||||
}
|
||||
|
||||
public func purchaseDate(forProduct product: LocalProduct) -> Date? {
|
||||
purchaseDates[product]
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: In-app eligibility
|
||||
|
||||
extension ProductManager {
|
||||
public func isCurrentPlatformVersion() -> Bool {
|
||||
purchasedFeatures.contains(isMac ? .fullVersion_macOS : .fullVersion_iOS)
|
||||
}
|
||||
|
||||
public func isFullVersion() -> Bool {
|
||||
if appType == .fullVersion {
|
||||
return true
|
||||
}
|
||||
if isCurrentPlatformVersion() {
|
||||
return true
|
||||
}
|
||||
return purchasedFeatures.contains(.fullVersion)
|
||||
}
|
||||
|
||||
public func isEligible(forFeature feature: LocalProduct) -> Bool {
|
||||
if let purchasedAppBuild = purchasedAppBuild {
|
||||
if feature == .networkSettings && buildProducts.hasProduct(.networkSettings, atBuild: purchasedAppBuild) {
|
||||
|
@ -206,9 +196,9 @@ extension ProductManager {
|
|||
}
|
||||
}
|
||||
if feature.isPlatformVersion {
|
||||
return purchasedFeatures.contains(feature)
|
||||
return isActivePurchase(feature)
|
||||
}
|
||||
return isFullVersion() || purchasedFeatures.contains(feature)
|
||||
return isFullVersion() || isActivePurchase(feature)
|
||||
}
|
||||
|
||||
public func isEligible(forProvider providerName: ProviderName) -> Bool {
|
||||
|
@ -221,16 +211,34 @@ extension ProductManager {
|
|||
public func isEligibleForFeedback() -> Bool {
|
||||
appType == .beta || !purchasedFeatures.isEmpty
|
||||
}
|
||||
|
||||
public func hasPurchased(_ product: LocalProduct) -> Bool {
|
||||
purchasedFeatures.contains(product)
|
||||
}
|
||||
|
||||
public func purchaseDate(forProduct product: LocalProduct) -> Date? {
|
||||
purchaseDates[product]
|
||||
extension ProductManager {
|
||||
func isActivePurchase(_ feature: LocalProduct) -> Bool {
|
||||
purchasedFeatures.contains(feature) && cancelledPurchases?.contains(feature) == false
|
||||
}
|
||||
|
||||
func isActivePurchase(where predicate: (LocalProduct) -> Bool) -> Bool {
|
||||
purchasedFeatures.contains(where: predicate) && cancelledPurchases?.contains(where: predicate) == false
|
||||
}
|
||||
|
||||
func isCurrentPlatformVersion() -> Bool {
|
||||
isActivePurchase(isMac ? .fullVersion_macOS : .fullVersion_iOS)
|
||||
}
|
||||
|
||||
func isFullVersion() -> Bool {
|
||||
if appType == .fullVersion {
|
||||
return true
|
||||
}
|
||||
if isCurrentPlatformVersion() {
|
||||
return true
|
||||
}
|
||||
return isActivePurchase(.fullVersion)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Receipt
|
||||
|
||||
extension ProductManager {
|
||||
public func reloadReceipt(andNotify: Bool = true) {
|
||||
guard let receipt = receiptReader.receipt(for: appType) else {
|
||||
|
@ -280,6 +288,8 @@ extension ProductManager {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: Helpers
|
||||
|
||||
private extension ProductManager {
|
||||
var isMac: Bool {
|
||||
#if targetEnvironment(macCatalyst)
|
||||
|
|
|
@ -120,6 +120,7 @@ final class ProductManagerTests: XCTestCase {
|
|||
reader.setReceipt(withBuild: 1500, products: [.fullVersion])
|
||||
let sut = ProductManager(inApp: inApp, receiptReader: reader, buildProducts: noBuildProducts)
|
||||
|
||||
XCTAssertTrue(sut.isFullVersion())
|
||||
XCTAssertTrue(LocalProduct
|
||||
.allFeatures
|
||||
.filter { !$0.isPlatformVersion }
|
||||
|
@ -132,6 +133,7 @@ final class ProductManagerTests: XCTestCase {
|
|||
reader.setReceipt(withBuild: 1500, products: [])
|
||||
let sut = ProductManager(inApp: inApp, receiptReader: reader, buildProducts: noBuildProducts)
|
||||
|
||||
XCTAssertFalse(sut.isFullVersion())
|
||||
XCTAssertFalse(LocalProduct
|
||||
.allFeatures
|
||||
.allSatisfy(sut.isEligible(forFeature:))
|
||||
|
|
Loading…
Reference in New Issue