处理storkit2
This commit is contained in:
154
keyBoard/Class/Pay/StoreKit2Manager/KBStoreKitBridge.swift
Normal file
154
keyBoard/Class/Pay/StoreKit2Manager/KBStoreKitBridge.swift
Normal file
@@ -0,0 +1,154 @@
|
||||
//
|
||||
// KBStoreKitBridge.swift
|
||||
// keyBoard
|
||||
//
|
||||
// Created to expose StoreKit2Manager to Objective-C callers.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import StoreKit
|
||||
|
||||
@objcMembers
|
||||
final class KBStoreKitBridge: NSObject, StoreKitDelegate {
|
||||
static let shared = KBStoreKitBridge()
|
||||
|
||||
private let manager = StoreKit2Manager.shared
|
||||
private let receiptVerifier = IAPVerifyTransactionObj()
|
||||
private var configuredProductIds: Set<String> = []
|
||||
private var configuredLifetimeIds: Set<String> = []
|
||||
private var hasConfigured = false
|
||||
|
||||
private override init() {
|
||||
super.init()
|
||||
}
|
||||
|
||||
// MARK: - Preparation
|
||||
|
||||
func prepare(withProductIds productIds: [String], completion: ((Bool, String?) -> Void)? = nil) {
|
||||
prepare(withProductIds: productIds, lifetimeIds: [], completion: completion)
|
||||
}
|
||||
|
||||
func prepare(withProductIds productIds: [String], lifetimeIds: [String], completion: ((Bool, String?) -> Void)? = nil) {
|
||||
Task {
|
||||
let success = await self.configureIfNeeded(productIds: productIds, lifetimeIds: lifetimeIds)
|
||||
await MainActor.run {
|
||||
completion?(success, success ? nil : "Unable to load products.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Purchase
|
||||
|
||||
func purchase(productId: String, completion: @escaping (Bool, String?) -> Void) {
|
||||
Task {
|
||||
let ready = await self.configureIfNeeded(productIds: [productId], lifetimeIds: [])
|
||||
guard ready else {
|
||||
await MainActor.run {
|
||||
completion(false, "Unable to load product.")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
try await self.manager.purchase(productId: productId)
|
||||
let state = self.manager.currentState
|
||||
switch state {
|
||||
case .purchaseSuccess(let id) where id == productId:
|
||||
let receipt = try await Self.fetchReceiptData()
|
||||
self.verifyReceipt(receipt, completion: completion)
|
||||
case .purchasePending(let id) where id == productId:
|
||||
await MainActor.run {
|
||||
completion(false, "Purchase pending approval.")
|
||||
}
|
||||
case .purchaseCancelled(let id) where id == productId:
|
||||
await MainActor.run {
|
||||
completion(false, "Purchase cancelled.")
|
||||
}
|
||||
case .purchaseFailed(let id, let error) where id == productId:
|
||||
await MainActor.run {
|
||||
completion(false, error.localizedDescription)
|
||||
}
|
||||
case .error(let error):
|
||||
await MainActor.run {
|
||||
completion(false, error.localizedDescription)
|
||||
}
|
||||
default:
|
||||
await MainActor.run {
|
||||
completion(false, "Purchase failed.")
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
await MainActor.run {
|
||||
completion(false, error.localizedDescription)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Private Helpers
|
||||
|
||||
@MainActor
|
||||
private func configureIfNeeded(productIds: [String], lifetimeIds: [String]) async -> Bool {
|
||||
let ids = productIds.filter { !$0.isEmpty }
|
||||
guard !ids.isEmpty else { return false }
|
||||
|
||||
let newProducts = Set(ids)
|
||||
let newLifetime = Set(lifetimeIds.filter { !$0.isEmpty })
|
||||
var needsConfigure = !hasConfigured
|
||||
|
||||
if !newProducts.isSubset(of: configuredProductIds) {
|
||||
configuredProductIds.formUnion(newProducts)
|
||||
needsConfigure = true
|
||||
}
|
||||
|
||||
if !newLifetime.isSubset(of: configuredLifetimeIds) {
|
||||
configuredLifetimeIds.formUnion(newLifetime)
|
||||
needsConfigure = true
|
||||
}
|
||||
|
||||
if needsConfigure {
|
||||
let config = StoreKitConfig(
|
||||
productIds: Array(configuredProductIds),
|
||||
lifetimeIds: Array(configuredLifetimeIds)
|
||||
)
|
||||
manager.configure(with: config, delegate: self)
|
||||
hasConfigured = true
|
||||
}
|
||||
|
||||
await manager.refreshProducts()
|
||||
return true
|
||||
}
|
||||
|
||||
private func verifyReceipt(_ receipt: String, completion: @escaping (Bool, String?) -> Void) {
|
||||
receiptVerifier.verifyReceipt(receipt) { success, message, _ in
|
||||
DispatchQueue.main.async {
|
||||
if success {
|
||||
NotificationCenter.default.post(
|
||||
name: NSNotification.Name(rawValue: NSNotification.Name.KBIAPDidCompletePurchase.rawValue),
|
||||
object: nil
|
||||
)
|
||||
}
|
||||
completion(success, message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static func fetchReceiptData() async throws -> String {
|
||||
if let receipt = try loadReceiptFromBundle() {
|
||||
return receipt
|
||||
}
|
||||
try await AppStore.sync()
|
||||
if let receipt = try loadReceiptFromBundle() {
|
||||
return receipt
|
||||
}
|
||||
throw StoreKitError.verificationFailed
|
||||
}
|
||||
|
||||
private static func loadReceiptFromBundle() throws -> String? {
|
||||
guard let url = Bundle.main.appStoreReceiptURL else { return nil }
|
||||
guard FileManager.default.fileExists(atPath: url.path) else { return nil }
|
||||
let data = try Data(contentsOf: url, options: .alwaysMapped)
|
||||
guard !data.isEmpty else { return nil }
|
||||
return data.base64EncodedString()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user