Deprecate StoreKit types
This commit is contained in:
parent
b0720dbe58
commit
bab5074eec
|
@ -3,12 +3,16 @@
|
|||
|
||||
## 1.3
|
||||
|
||||
This version adjusts the library for Xcode 14.
|
||||
This version adjusts the library for Xcode 14 and deprecates some things.
|
||||
|
||||
### ✨ New features
|
||||
|
||||
* `Collection+Async` is now available for all OS versions that are supported by the library.
|
||||
|
||||
### 🗑 Deprecations
|
||||
|
||||
* The `StoreKit` namespace has been deprecated and moved to a new library: https://github.com/danielsaidi/StoreKitPlus
|
||||
|
||||
|
||||
|
||||
## 1.2
|
||||
|
|
|
@ -8,28 +8,10 @@
|
|||
|
||||
import StoreKit
|
||||
|
||||
/**
|
||||
This service class implements `StoreService` by integrating
|
||||
with StoreKit.
|
||||
|
||||
The service keeps products and purchases in sync, using the
|
||||
provided ``StoreContext``. An app can listen to any changes
|
||||
in the context to drive UI changes.
|
||||
|
||||
You can configure a test app to use this service with local
|
||||
products by adding a StoreKit configuration file to the app.
|
||||
*/
|
||||
@available(*, deprecated, message: "StandardStoreService has been moved to StoreKitPlus - https://github.com/danielsaidi/StoreKitPlus")
|
||||
@available(iOS 15.0, macOS 12.0, watchOS 8.0, tvOS 15.0, *)
|
||||
public class StandardStoreService: StoreService {
|
||||
|
||||
/**
|
||||
Create a service instance for the provided `productIds`,
|
||||
that syncs any changes to the provided `context`.
|
||||
|
||||
- Parameters:
|
||||
- productIds: The IDs of the products to handle.
|
||||
- context: The store context to sync with.
|
||||
*/
|
||||
public init(
|
||||
productIds: [String],
|
||||
context: StoreContext = StoreContext()) {
|
||||
|
@ -47,16 +29,10 @@ public class StandardStoreService: StoreService {
|
|||
private let storeContext: StoreContext
|
||||
private var transactionTask: Task<Void, Error>?
|
||||
|
||||
/**
|
||||
Get all available products.
|
||||
*/
|
||||
public func getProducts() async throws -> [Product] {
|
||||
try await Product.products(for: productIds)
|
||||
}
|
||||
|
||||
/**
|
||||
Purchase a certain product.
|
||||
*/
|
||||
public func purchase(_ product: Product) async throws -> Product.PurchaseResult {
|
||||
let result = try await product.purchase()
|
||||
switch result {
|
||||
|
@ -68,17 +44,10 @@ public class StandardStoreService: StoreService {
|
|||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
Restore purchases that are not on this device.
|
||||
*/
|
||||
public func restorePurchases() async throws {
|
||||
try await syncTransactions()
|
||||
}
|
||||
|
||||
/**
|
||||
Sync product and purchase information from the store to
|
||||
the provided store context.
|
||||
*/
|
||||
public func syncStoreData() async throws {
|
||||
let products = try await getProducts()
|
||||
await updateContext(with: products)
|
||||
|
@ -86,13 +55,10 @@ public class StandardStoreService: StoreService {
|
|||
}
|
||||
}
|
||||
|
||||
@available(*, deprecated, message: "StandardStoreService has been moved to StoreKitPlus - https://github.com/danielsaidi/StoreKitPlus")
|
||||
@available(iOS 15.0, macOS 12.0, watchOS 8.0, tvOS 15.0, *)
|
||||
private extension StandardStoreService {
|
||||
|
||||
/**
|
||||
Create a task that can be used to listen for and acting
|
||||
on transaction changes.
|
||||
*/
|
||||
func getTransactionListenerTask() -> Task<Void, Error> {
|
||||
Task.detached {
|
||||
for await result in Transaction.updates {
|
||||
|
@ -105,27 +71,18 @@ private extension StandardStoreService {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Try resolving a valid transaction for a certain product.
|
||||
*/
|
||||
func getValidTransaction(for productId: String) async throws -> Transaction? {
|
||||
guard let latest = await Transaction.latest(for: productId) else { return nil }
|
||||
let result = try verifyTransaction(latest)
|
||||
return result.isValid ? result : nil
|
||||
}
|
||||
|
||||
/**
|
||||
Handle the transaction in the provided `result`.
|
||||
*/
|
||||
func handleTransaction(_ result: VerificationResult<Transaction>) async throws {
|
||||
let transaction = try verifyTransaction(result)
|
||||
await updateContext(with: transaction)
|
||||
await transaction.finish()
|
||||
}
|
||||
|
||||
/**
|
||||
Sync the transactions of all available products.
|
||||
*/
|
||||
func syncTransactions() async throws {
|
||||
var transactions: [Transaction] = []
|
||||
for id in productIds {
|
||||
|
@ -136,9 +93,6 @@ private extension StandardStoreService {
|
|||
await updateContext(with: transactions)
|
||||
}
|
||||
|
||||
/**
|
||||
Verify the transaction in the provided `result`
|
||||
*/
|
||||
func verifyTransaction(_ result: VerificationResult<Transaction>) throws -> Transaction {
|
||||
switch result {
|
||||
case .unverified(let transaction, let error): throw StoreServiceError.invalidTransaction(transaction, error)
|
||||
|
@ -148,6 +102,7 @@ private extension StandardStoreService {
|
|||
}
|
||||
|
||||
@MainActor
|
||||
@available(*, deprecated, message: "StandardStoreService has been moved to StoreKitPlus - https://github.com/danielsaidi/StoreKitPlus")
|
||||
@available(iOS 15.0, macOS 12.0, watchOS 8.0, tvOS 15.0, *)
|
||||
private extension StandardStoreService {
|
||||
|
||||
|
|
|
@ -1,92 +0,0 @@
|
|||
//
|
||||
// StoreContext.swift
|
||||
// SwiftKit
|
||||
//
|
||||
// Created by Daniel Saidi on 2021-11-08.
|
||||
// Copyright © 2021 Daniel Saidi. All rights reserved.
|
||||
//
|
||||
|
||||
import StoreKit
|
||||
import Combine
|
||||
|
||||
/**
|
||||
This class can be used to manage store information in a way
|
||||
that makes it observable.
|
||||
|
||||
Since the `Product` type isn't `Codable`, `products` is not
|
||||
permanently persisted, which means that this information is
|
||||
reset if the app restarts. Due to this, any changes to this
|
||||
property will also update `productIds`, which gives you the
|
||||
option to map the IDs to a local product representation and
|
||||
present previously fetched products.
|
||||
|
||||
Note however that a `Product` instance is needed to perform
|
||||
a purchase.
|
||||
*/
|
||||
@available(iOS 15.0, macOS 12.0, watchOS 8.0, tvOS 15.0, *)
|
||||
public class StoreContext: ObservableObject {
|
||||
|
||||
public init() {
|
||||
productIds = persistedProductIds
|
||||
purchasedProductIds = persistedPurchasedProductIds
|
||||
}
|
||||
|
||||
/**
|
||||
The available products.
|
||||
|
||||
Since `Product` isn't `Codable`, this property can't be
|
||||
persisted. It must be re-fetched when the app starts.
|
||||
*/
|
||||
public var products: [Product] = [] {
|
||||
didSet { productIds = products.map { $0.id} }
|
||||
}
|
||||
|
||||
/**
|
||||
The available products IDs.
|
||||
|
||||
This list is permanently persisted and can be mapped to
|
||||
a local product representation to present temp. product
|
||||
info before `products` have been re-fetched.
|
||||
*/
|
||||
@Published
|
||||
public private(set) var productIds: [String] = [] {
|
||||
willSet { persistedProductIds = newValue }
|
||||
}
|
||||
|
||||
/**
|
||||
The purchased products IDs.
|
||||
|
||||
This list is permanently persisted and can be mapped to
|
||||
a local product representation to present temp. product
|
||||
info before `transactions` have been re-fetched.
|
||||
*/
|
||||
@Published
|
||||
public private(set) var purchasedProductIds: [String] = [] {
|
||||
willSet { persistedPurchasedProductIds = newValue }
|
||||
}
|
||||
|
||||
/**
|
||||
The active transactions.
|
||||
|
||||
Since `Transaction` isn't `Codable` this property can't
|
||||
be persisted. It must be re-fetched when the app starts.
|
||||
*/
|
||||
public var transactions: [StoreKit.Transaction] = [] {
|
||||
didSet { purchasedProductIds = transactions.map { $0.productID } }
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Persisted Properties
|
||||
|
||||
@Persisted(key: key("productIds"), defaultValue: [])
|
||||
private var persistedProductIds: [String]
|
||||
|
||||
@Persisted(key: key("purchasedProductIds"), defaultValue: [])
|
||||
private var persistedPurchasedProductIds: [String]
|
||||
}
|
||||
|
||||
@available(iOS 15.0, macOS 12.0, watchOS 8.0, tvOS 15.0, *)
|
||||
private extension StoreContext {
|
||||
|
||||
static func key(_ name: String) -> String { "store.\(name)" }
|
||||
}
|
|
@ -8,19 +8,14 @@
|
|||
|
||||
import StoreKit
|
||||
|
||||
@available(*, deprecated, message: "StoreContext has been moved to StoreKitPlus - https://github.com/danielsaidi/StoreKitPlus")
|
||||
@available(iOS 15.0, macOS 12.0, watchOS 8.0, tvOS 15.0, *)
|
||||
public extension StoreContext {
|
||||
|
||||
/**
|
||||
Whether or not a certain product has an active purchase.
|
||||
*/
|
||||
func isProductPurchased(id: String) -> Bool {
|
||||
purchasedProductIds.contains(id)
|
||||
}
|
||||
|
||||
/**
|
||||
Whether or not a certain product has an active purchase.
|
||||
*/
|
||||
func isProductPurchased(_ product: Product) -> Bool {
|
||||
isProductPurchased(id: product.id)
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
//
|
||||
// StoreContext.swift
|
||||
// SwiftKit
|
||||
//
|
||||
// Created by Daniel Saidi on 2021-11-08.
|
||||
// Copyright © 2021 Daniel Saidi. All rights reserved.
|
||||
//
|
||||
|
||||
import StoreKit
|
||||
import Combine
|
||||
|
||||
@available(*, deprecated, message: "StoreContext has been moved to StoreKitPlus - https://github.com/danielsaidi/StoreKitPlus")
|
||||
@available(iOS 15.0, macOS 12.0, watchOS 8.0, tvOS 15.0, *)
|
||||
public class StoreContext: ObservableObject {
|
||||
|
||||
public init() {
|
||||
productIds = persistedProductIds
|
||||
purchasedProductIds = persistedPurchasedProductIds
|
||||
}
|
||||
|
||||
public var products: [Product] = [] {
|
||||
didSet { productIds = products.map { $0.id} }
|
||||
}
|
||||
|
||||
@Published
|
||||
public private(set) var productIds: [String] = [] {
|
||||
willSet { persistedProductIds = newValue }
|
||||
}
|
||||
|
||||
@Published
|
||||
public private(set) var purchasedProductIds: [String] = [] {
|
||||
willSet { persistedPurchasedProductIds = newValue }
|
||||
}
|
||||
|
||||
public var transactions: [StoreKit.Transaction] = [] {
|
||||
didSet { purchasedProductIds = transactions.map { $0.productID } }
|
||||
}
|
||||
|
||||
|
||||
@Persisted(key: key("productIds"), defaultValue: [])
|
||||
private var persistedProductIds: [String]
|
||||
|
||||
@Persisted(key: key("purchasedProductIds"), defaultValue: [])
|
||||
private var persistedPurchasedProductIds: [String]
|
||||
}
|
||||
|
||||
@available(*, deprecated, message: "StoreContext has been moved to StoreKitPlus - https://github.com/danielsaidi/StoreKitPlus")
|
||||
@available(iOS 15.0, macOS 12.0, watchOS 8.0, tvOS 15.0, *)
|
||||
private extension StoreContext {
|
||||
|
||||
static func key(_ name: String) -> String { "store.\(name)" }
|
||||
}
|
|
@ -8,31 +8,12 @@
|
|||
|
||||
import StoreKit
|
||||
|
||||
/**
|
||||
This protocol can be implemented by any classes that can be
|
||||
used to manage store products.
|
||||
*/
|
||||
@available(*, deprecated, message: "StoreService has been moved to StoreKitPlus - https://github.com/danielsaidi/StoreKitPlus")
|
||||
@available(iOS 15.0, macOS 12.0, watchOS 8.0, tvOS 15.0, *)
|
||||
public protocol StoreService {
|
||||
|
||||
/**
|
||||
Get all available products.
|
||||
*/
|
||||
func getProducts() async throws -> [Product]
|
||||
|
||||
/**
|
||||
Purchase a certain product.
|
||||
*/
|
||||
func purchase(_ product: Product) async throws -> Product.PurchaseResult
|
||||
|
||||
/**
|
||||
Restore purchases that are not on this device.
|
||||
*/
|
||||
func restorePurchases() async throws
|
||||
|
||||
/**
|
||||
Sync product and purchase information from the store to
|
||||
any implementation defined sync destination.
|
||||
*/
|
||||
func syncStoreData() async throws
|
||||
}
|
|
@ -9,12 +9,9 @@
|
|||
import Foundation
|
||||
import StoreKit
|
||||
|
||||
/**
|
||||
This enum lists errors that can be thrown by store services.
|
||||
*/
|
||||
@available(*, deprecated, message: "StoreServiceError has been moved to StoreKitPlus - https://github.com/danielsaidi/StoreKitPlus")
|
||||
@available(iOS 15.0, macOS 12.0, watchOS 8.0, tvOS 15.0, *)
|
||||
public enum StoreServiceError: Error {
|
||||
|
||||
/// This is thrown if a transaction can't be verified.
|
||||
case invalidTransaction(Transaction, VerificationResult<Transaction>.VerificationError)
|
||||
}
|
|
@ -8,15 +8,10 @@
|
|||
|
||||
import StoreKit
|
||||
|
||||
@available(*, deprecated, message: "This extension has been moved to StoreKitPlus - https://github.com/danielsaidi/StoreKitPlus")
|
||||
@available(iOS 15.0, macOS 12.0, watchOS 8.0, tvOS 15.0, *)
|
||||
extension Transaction {
|
||||
|
||||
/**
|
||||
Whether or not the transaction is valid.
|
||||
|
||||
The logic may have to be adjusted as more product types
|
||||
are handled with this service.
|
||||
*/
|
||||
var isValid: Bool {
|
||||
if revocationDate != nil { return false }
|
||||
guard let date = expirationDate else { return false }
|
Loading…
Reference in New Issue