Files
keyboard/keyBoard/Class/Pay/StoreKit2Manager/KBStoreKitBridge.swift

152 lines
5.0 KiB
Swift
Raw Normal View History

2025-12-16 13:49:08 +08:00
//
// 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)
2025-12-16 14:14:49 +08:00
2025-12-16 15:47:12 +08:00
if let payload = await self.fetchPayload(for: productId) {
2025-12-16 14:14:49 +08:00
self.verifySignedPayload(payload, completion: completion)
} else {
2025-12-16 13:49:08 +08:00
await MainActor.run {
2025-12-16 16:03:14 +08:00
completion(false, self.failureMessage(for: productId))
2025-12-16 13:49:08 +08:00
}
}
} catch {
await MainActor.run {
completion(false, error.localizedDescription)
}
}
}
}
2025-12-16 16:25:50 +08:00
func restorePurchases(completion: @escaping (Bool, String?) -> Void) {
Task {
do {
try await self.manager.restorePurchases()
await MainActor.run {
completion(true, nil)
}
} catch {
await MainActor.run {
completion(false, error.localizedDescription)
}
}
}
}
2025-12-16 13:49:08 +08:00
// 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
}
2025-12-16 14:14:49 +08:00
private func verifySignedPayload(_ payload: String, completion: @escaping (Bool, String?) -> Void) {
receiptVerifier.verifySignedPayload(payload) { success, message, _ in
2025-12-16 13:49:08 +08:00
DispatchQueue.main.async {
if success {
NotificationCenter.default.post(
2025-12-16 14:14:49 +08:00
name: NSNotification.Name.KBIAPDidCompletePurchase,
2025-12-16 13:49:08 +08:00
object: nil
)
}
completion(success, message)
}
}
}
2025-12-16 15:47:12 +08:00
@MainActor
private func fetchPayload(for productId: String) async -> String? {
2025-12-16 16:00:00 +08:00
return manager.consumeRecentPayload(for: productId)
2025-12-16 13:49:08 +08:00
}
2025-12-16 16:03:14 +08:00
@MainActor
private func failureMessage(for productId: String) -> String {
switch manager.currentState {
case .purchaseCancelled(let id) where id == productId:
return "Purchase cancelled."
case .purchasePending(let id) where id == productId:
return "Purchase pending approval."
case .purchaseFailed(let id, let error) where id == productId:
return error.localizedDescription
default:
return "Unable to obtain transaction payload."
}
}
2025-12-16 13:49:08 +08:00
}