Incorrect handling of receipt purchases (#439)
This commit is contained in:
parent
1551b59f21
commit
4c4876c5f7
|
@ -32,7 +32,7 @@
|
||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>2.2.1</string>
|
<string>2.3.0</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>3548</string>
|
<string>3548</string>
|
||||||
<key>ITSAppUsesNonExemptEncryption</key>
|
<key>ITSAppUsesNonExemptEncryption</key>
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>2.2.1</string>
|
<string>2.3.0</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>3548</string>
|
<string>3548</string>
|
||||||
<key>LSBackgroundOnly</key>
|
<key>LSBackgroundOnly</key>
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>2.2.1</string>
|
<string>2.3.0</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>3548</string>
|
<string>3548</string>
|
||||||
<key>NSPrincipalClass</key>
|
<key>NSPrincipalClass</key>
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>XPC!</string>
|
<string>XPC!</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>2.2.1</string>
|
<string>2.3.0</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>3548</string>
|
<string>3548</string>
|
||||||
<key>LSMinimumSystemVersion</key>
|
<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 PassepartoutCore
|
||||||
import PassepartoutProviders
|
import PassepartoutProviders
|
||||||
|
|
||||||
public protocol LocalInApp: InAppProtocol where ProductIdentifier == LocalProduct {
|
|
||||||
}
|
|
||||||
|
|
||||||
extension StoreKitInApp: LocalInApp where ProductIdentifier == LocalProduct {
|
|
||||||
}
|
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
public final class ProductManager: NSObject, ObservableObject {
|
public final class ProductManager: NSObject, ObservableObject {
|
||||||
private let inApp: any LocalInApp
|
private let inApp: any LocalInApp
|
||||||
|
@ -106,6 +100,8 @@ public final class ProductManager: NSObject, ObservableObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: Main interface
|
||||||
|
|
||||||
public func canMakePayments() -> Bool {
|
public func canMakePayments() -> Bool {
|
||||||
inApp.canMakePurchases()
|
inApp.canMakePurchases()
|
||||||
}
|
}
|
||||||
|
@ -180,25 +176,19 @@ public final class ProductManager: NSObject, ObservableObject {
|
||||||
public func restorePurchases() async throws {
|
public func restorePurchases() async throws {
|
||||||
try await inApp.restorePurchases()
|
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
|
// MARK: In-app eligibility
|
||||||
|
|
||||||
extension ProductManager {
|
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 {
|
public func isEligible(forFeature feature: LocalProduct) -> Bool {
|
||||||
if let purchasedAppBuild = purchasedAppBuild {
|
if let purchasedAppBuild = purchasedAppBuild {
|
||||||
if feature == .networkSettings && buildProducts.hasProduct(.networkSettings, atBuild: purchasedAppBuild) {
|
if feature == .networkSettings && buildProducts.hasProduct(.networkSettings, atBuild: purchasedAppBuild) {
|
||||||
|
@ -206,9 +196,9 @@ extension ProductManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if feature.isPlatformVersion {
|
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 {
|
public func isEligible(forProvider providerName: ProviderName) -> Bool {
|
||||||
|
@ -221,16 +211,34 @@ extension ProductManager {
|
||||||
public func isEligibleForFeedback() -> Bool {
|
public func isEligibleForFeedback() -> Bool {
|
||||||
appType == .beta || !purchasedFeatures.isEmpty
|
appType == .beta || !purchasedFeatures.isEmpty
|
||||||
}
|
}
|
||||||
|
|
||||||
public func hasPurchased(_ product: LocalProduct) -> Bool {
|
|
||||||
purchasedFeatures.contains(product)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public func purchaseDate(forProduct product: LocalProduct) -> Date? {
|
extension ProductManager {
|
||||||
purchaseDates[product]
|
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 {
|
extension ProductManager {
|
||||||
public func reloadReceipt(andNotify: Bool = true) {
|
public func reloadReceipt(andNotify: Bool = true) {
|
||||||
guard let receipt = receiptReader.receipt(for: appType) else {
|
guard let receipt = receiptReader.receipt(for: appType) else {
|
||||||
|
@ -280,6 +288,8 @@ extension ProductManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: Helpers
|
||||||
|
|
||||||
private extension ProductManager {
|
private extension ProductManager {
|
||||||
var isMac: Bool {
|
var isMac: Bool {
|
||||||
#if targetEnvironment(macCatalyst)
|
#if targetEnvironment(macCatalyst)
|
||||||
|
|
|
@ -120,6 +120,7 @@ final class ProductManagerTests: XCTestCase {
|
||||||
reader.setReceipt(withBuild: 1500, products: [.fullVersion])
|
reader.setReceipt(withBuild: 1500, products: [.fullVersion])
|
||||||
let sut = ProductManager(inApp: inApp, receiptReader: reader, buildProducts: noBuildProducts)
|
let sut = ProductManager(inApp: inApp, receiptReader: reader, buildProducts: noBuildProducts)
|
||||||
|
|
||||||
|
XCTAssertTrue(sut.isFullVersion())
|
||||||
XCTAssertTrue(LocalProduct
|
XCTAssertTrue(LocalProduct
|
||||||
.allFeatures
|
.allFeatures
|
||||||
.filter { !$0.isPlatformVersion }
|
.filter { !$0.isPlatformVersion }
|
||||||
|
@ -132,6 +133,7 @@ final class ProductManagerTests: XCTestCase {
|
||||||
reader.setReceipt(withBuild: 1500, products: [])
|
reader.setReceipt(withBuild: 1500, products: [])
|
||||||
let sut = ProductManager(inApp: inApp, receiptReader: reader, buildProducts: noBuildProducts)
|
let sut = ProductManager(inApp: inApp, receiptReader: reader, buildProducts: noBuildProducts)
|
||||||
|
|
||||||
|
XCTAssertFalse(sut.isFullVersion())
|
||||||
XCTAssertFalse(LocalProduct
|
XCTAssertFalse(LocalProduct
|
||||||
.allFeatures
|
.allFeatures
|
||||||
.allSatisfy(sut.isEligible(forFeature:))
|
.allSatisfy(sut.isEligible(forFeature:))
|
||||||
|
|
Loading…
Reference in New Issue