Rework how purchasable products are presented (#465)

Fixes #464
This commit is contained in:
Davide De Rosa 2024-01-11 16:46:52 +01:00 committed by GitHub
parent 63cbf39a8a
commit e21e11b022
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 118 additions and 24 deletions

View File

@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Unreleased
### Fixed
- Platform purchasers cannot upgrade to full version. [#464](https://github.com/passepartoutvpn/passepartout-apple/issues/464)
## 2.3.2 (2024-01-11) ## 2.3.2 (2024-01-11)
### Fixed ### Fixed

View File

@ -194,29 +194,8 @@ private extension PaywallView.PurchaseView {
} }
private extension PaywallView.PurchaseView { private extension PaywallView.PurchaseView {
// hide full version if already bought the other platform version
var skFullVersion: InAppProduct? { var skFullVersion: InAppProduct? {
guard !productManager.isFullVersion() else { productManager.product(withIdentifier: .fullVersion)
return nil
}
#if targetEnvironment(macCatalyst)
guard !productManager.hasPurchased(.fullVersion_iOS) else {
return nil
}
#else
guard !productManager.hasPurchased(.fullVersion_macOS) else {
return nil
}
#endif
return productManager.product(withIdentifier: .fullVersion)
}
var skAppleTV: InAppProduct? {
guard feature == .appleTV else {
return nil
}
return productManager.product(withIdentifier: .appleTV)
} }
var fullFeatures: [FeatureModel] { var fullFeatures: [FeatureModel] {
@ -230,8 +209,9 @@ private extension PaywallView.PurchaseView {
} }
var productRowModels: [InAppProduct] { var productRowModels: [InAppProduct] {
[skFullVersion, skAppleTV] productManager
.compactMap { $0 } .purchasableProducts(withFeature: feature)
.compactMap { productManager.product(withIdentifier: $0) }
} }
} }

View File

@ -293,6 +293,44 @@ extension ProductManager {
} }
} }
// MARK: Purchasable products
extension ProductManager {
// no purchase -> full version or platform version
// purchased platform -> may only purchase other platform
public func purchasableProducts(withFeature feature: LocalProduct?) -> [LocalProduct] {
var products: [LocalProduct] = {
if hasPurchased(.fullVersion) {
return []
}
#if targetEnvironment(macCatalyst)
if hasPurchased(.fullVersion_macOS) {
return []
}
if hasPurchased(.fullVersion_iOS) {
return [.fullVersion_macOS]
}
return [.fullVersion, .fullVersion_macOS]
#else
if hasPurchased(.fullVersion_iOS) {
return []
}
if hasPurchased(.fullVersion_macOS) {
return [.fullVersion_iOS]
}
return [.fullVersion, .fullVersion_iOS]
#endif
}()
if feature == .appleTV && !hasPurchased(.appleTV) {
products.append(.appleTV)
}
return products
}
}
// MARK: Helpers // MARK: Helpers
private extension ProductManager { private extension ProductManager {

View File

@ -33,6 +33,8 @@ final class ProductManagerTests: XCTestCase {
private let noBuildProducts = BuildProducts { _ in [] } private let noBuildProducts = BuildProducts { _ in [] }
// MARK: Build products
func test_givenBuildProducts_whenOlder_thenFullVersion() { func test_givenBuildProducts_whenOlder_thenFullVersion() {
let reader = MockReceiptReader() let reader = MockReceiptReader()
reader.setReceipt(withBuild: 500, products: []) reader.setReceipt(withBuild: 500, products: [])
@ -57,6 +59,8 @@ final class ProductManagerTests: XCTestCase {
XCTAssertFalse(sut.isEligible(forFeature: .fullVersion)) XCTAssertFalse(sut.isEligible(forFeature: .fullVersion))
} }
// MARK: Eligibility
func test_givenPurchase_whenReload_thenCredited() { func test_givenPurchase_whenReload_thenCredited() {
let reader = MockReceiptReader() let reader = MockReceiptReader()
let sut = ProductManager(inApp: inApp, receiptReader: reader, buildProducts: noBuildProducts) let sut = ProductManager(inApp: inApp, receiptReader: reader, buildProducts: noBuildProducts)
@ -163,4 +167,70 @@ final class ProductManagerTests: XCTestCase {
XCTAssertTrue(sut.isEligible(forFeature: .appleTV)) XCTAssertTrue(sut.isEligible(forFeature: .appleTV))
} }
// MARK: Purchasable
func test_givenNoPurchase_thenCanBuyFullAndPlatformVersion() {
let reader = MockReceiptReader()
reader.setReceipt(withBuild: 1500, products: [])
let sut = ProductManager(inApp: inApp, receiptReader: reader, buildProducts: noBuildProducts)
#if targetEnvironment(macCatalyst)
XCTAssertEqual(sut.purchasableProducts(withFeature: nil), [.fullVersion, .fullVersion_macOS])
#else
XCTAssertEqual(sut.purchasableProducts(withFeature: nil), [.fullVersion, .fullVersion_iOS])
#endif
}
func test_givenFullVersion_thenCannotPurchase() {
let reader = MockReceiptReader()
reader.setReceipt(withBuild: 1500, products: [.fullVersion])
let sut = ProductManager(inApp: inApp, receiptReader: reader, buildProducts: noBuildProducts)
XCTAssertEqual(sut.purchasableProducts(withFeature: nil), [])
}
func test_givenPlatformVersion_thenCannotPurchaseSamePlatform() {
let reader = MockReceiptReader()
#if targetEnvironment(macCatalyst)
reader.setReceipt(withBuild: 1500, products: [.fullVersion_macOS])
let sut = ProductManager(inApp: inApp, receiptReader: reader, buildProducts: noBuildProducts)
XCTAssertEqual(sut.purchasableProducts(withFeature: nil), [])
#else
reader.setReceipt(withBuild: 1500, products: [.fullVersion_iOS])
let sut = ProductManager(inApp: inApp, receiptReader: reader, buildProducts: noBuildProducts)
XCTAssertEqual(sut.purchasableProducts(withFeature: nil), [])
#endif
}
func test_givenOtherPlatformVersion_thenCanOnlyPurchaseMissingPlatform() {
let reader = MockReceiptReader()
#if targetEnvironment(macCatalyst)
reader.setReceipt(withBuild: 1500, products: [.fullVersion_iOS])
let sut = ProductManager(inApp: inApp, receiptReader: reader, buildProducts: noBuildProducts)
XCTAssertEqual(sut.purchasableProducts(withFeature: nil), [.fullVersion_macOS])
#else
reader.setReceipt(withBuild: 1500, products: [.fullVersion_macOS])
let sut = ProductManager(inApp: inApp, receiptReader: reader, buildProducts: noBuildProducts)
XCTAssertEqual(sut.purchasableProducts(withFeature: nil), [.fullVersion_iOS])
#endif
}
func test_givenAppleTV_whenDidNotPurchase_thenCanPurchase() {
let reader = MockReceiptReader()
reader.setReceipt(withBuild: 1500, products: [])
let sut = ProductManager(inApp: inApp, receiptReader: reader, buildProducts: noBuildProducts)
XCTAssertTrue(sut.purchasableProducts(withFeature: .appleTV).contains(.appleTV))
}
func test_givenAppleTV_whenDidPurchase_thenCannotPurchase() {
let reader = MockReceiptReader()
reader.setReceipt(withBuild: 1500, products: [.appleTV])
let sut = ProductManager(inApp: inApp, receiptReader: reader, buildProducts: noBuildProducts)
XCTAssertFalse(sut.purchasableProducts(withFeature: .appleTV).contains(.appleTV))
}
} }