
Это совершенно новый фреймворк, написанный на чистом Swift с использованием нового синтаксиса await/async. Главные особенности:
SKPaymentTransactionObserver. Теперь процесс покупки происходит с помощью await. Product и Transaction теперь структуры вместо классов.
Продукты в StoreKit 2Product стал структурой и запрос на получение данных по product id теперь осуществляется с помощью static метода:
public static func request(with identifiers: Set<String>) async throws -> [Product]
На практике это выглядит так:
func loadProducts() async {
do {
let ids = [ "com.apphud.weekly", "com.apphud.weekly2", "com.apphud.monthly"]
self.products = try await Product.request(with: Set(ids))
} catch {
print("error while loading products: \(error.localizedDescription)")
}
}Все намного проще, не правда ли?
Так же у Product появились новые методы, например, структура ProductType, которая возвращает тип встроенной покупки:
public static var consumable: Product.ProductType public static var nonConsumable: Product.ProductType public static var nonRenewable: Product.ProductType public static var autoRenewable: Product.ProductType
Или, например, отформатированная цена в виде строки:
/// A localized string representation of `price`. public let displayPrice: String
Еще одним крутым нововведением являются методы проверки доступности триала:
/// Whether the user is eligible to have an introductory offer applied to their purchase.
public var isEligibleForIntroOffer: Bool { get async }
public static func isEligibleForIntroOffer(for groupID: String) async -> BoolПервый getter метод проверяет конкретно данный Product, а второй – static метод, принимающий ID группы подписки в качестве параметра.
Apple добавила анонимный идентификатор пользователя к Transaction, который остается навсегда и доступен, в том числе в REST API. Это означает, что теперь можно будет однозначно сопоставлять пользователя приложения с данными по транзакциям из REST API.
/// A UUID that associates the purchase with an account in your system. public static func appAccountToken(_ token: UUID) -> Product.PurchaseOption
Процесс покупки также значительно упростили, убрав тяжелый SKPaymentTransactionObserver.
let result = try await product.purchase()
Результатом покупки будет enumPurchaseResult:
public enum PurchaseResult {
/// The purchase succeeded with a `Transaction`.
case success(VerificationResult<Transaction>)
/// The user cancelled the purchase.
case userCancelled
/// The purchase is pending some user action.
///
/// These purchases may succeed in the future, and the resulting `Transaction` will be
/// delivered via `Transaction.listener`
case pending
}А если произошла какая-то ошибка, то она вернется в catch.
Ниже приведен пример использования метода покупки:
func purchase(_ product: Product) async throws -> Transaction? {
//Begin a purchase.
let result = try await product.purchase()
switch result {
case .success(let verification):
let transaction = try checkVerified(verification)
//Deliver content to the user.
await updatePurchasedIdentifiers(transaction)
//Always finish a transaction.
await transaction.finish()
return transaction
case .userCancelled, .pending:
return nil
default:
return nil
}
}
func checkVerified<T>(_ result: VerificationResult<T>) throws -> T {
//Check if the transaction passes StoreKit verification.
switch result {
case .unverified:
//StoreKit has parsed the JWS but failed verification. Don't deliver content to the user.
throw StoreError.failedVerification
case .verified(let safe):
//If the transaction is verified, unwrap and return it.
return safe
}
}Чтобы запустить асинхронный код внутри синхронного, достаточно обернуть его в async {}:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let product = products[indexPath.row]
async {
await self.purchaseAsync(product: product)
}
}Вместо Transaction Observer Apple добавили свой Transaction Listener. Он отдает транзакции, которые не пришли в прямом вызове метода purchase().
// Start a transaction listener as close to app launch as possible so you don't miss any transactions.
taskHandle = listenForTransactions()
func listenForTransactions() -> Task.Handle<Void, Error> {
return detach {
//Iterate through any transactions which didn't come from a direct call to `purchase()`.
for await result in Transaction.listener {
do {
let transaction = try self.checkVerified(result)
//Deliver content to the user.
await self.updatePurchasedIdentifiers(transaction)
//Always finish a transaction.
await transaction.finish()
} catch {
//StoreKit has a receipt it can read but it failed verification. Don't deliver content to the user.
print("Transaction failed verification")
}
}
}
}Transaction Listener может использоваться для случаев Ask To Buy или Strong Customer Authentication, когда транзакция переходит в состояние pending.
StoreKit 2 также может возвращать историю транзакций в структуре TransactionSequence. Можно получить все транзакции, либо только последнюю, либо получить Entitlements – набор транзакций, которые отражают состояние всех приобретенных подписок и разовых покупок.
История транзакций в StoreKit 2Например, если в вашем приложении есть и подписка, и non-consumable покупка, и пользователь приобрел оба продукта, то currentEntitlements вернет последнюю активную транзакцию для подписки и транзакцию для non-consumable покупки.
/// A sequence of every transaction for this user and app.
public static var all: Transaction.TransactionSequence { get }
/// Returns all transactions for products the user is currently entitled to
///
/// i.e. all currently-subscribed transactions, and all purchased (and not refunded) non-consumables
public static var currentEntitlements: Transaction.TransactionSequence { get }
/// Get the transaction that entitles the user to a product.
/// - Parameter productID: Identifies the product to check entitlements for.
/// - Returns: A transaction if the user is entitled to the product, or `nil` if they are not.
public static func currentEntitlement(for productID: String) async -> VerificationResult<Transaction>?
/// The user's latest transaction for a product.
/// - Parameter productID: Identifies the product to check entitlements for.
/// - Returns: A verified transaction, or `nil` if the user has never purchased this product.
public static func latest(for productID: String) async -> VerificationResult<Transaction>?
Одной из главных особенностей нового StoreKit 2 API является улучшенная работа с подписками. Теперь получать важную информацию о подписке можно прямо из приложения!
Что доступно:
subscribed, expired, inBillingRetryPeriod, inGracePeriod, revoked.autoRenewalStatus, gracePeriodExpirationDate. Что соответствует хешу pending_renewal_info из текущего verifyReceipt эндпоинта.
StoreKit 2 APIВсе транзакции доступны сразу после скачивания приложения и синхронизируются автоматически на каждом устройстве. Таким образом, несмотря на то, что в StoreKit 2 присутствует метод sync(), он будет использоваться крайне редко. Можно сказать, что кнопка Restore Purchases станет неактуальна и можно будет спрятать ее глубоко в UI – покупки восстанавливаются автоматически.
StoreKit 2 безусловно заметно упрощает жизнь разработчикам приложений для определения статуса подписки, что положительно влияет на Apphud.