533 lines
22 KiB
Swift
533 lines
22 KiB
Swift
//
|
||
// MP_IAPManager.swift
|
||
// relax.offline.mp3.music
|
||
//
|
||
// Created by Mr.Zhou on 2024/7/29.
|
||
//
|
||
|
||
import UIKit
|
||
import SwiftyStoreKit
|
||
import StoreKit
|
||
///内部购买项目管理器
|
||
class MP_IAPManager: NSObject {
|
||
///单例
|
||
static let shared = MP_IAPManager()
|
||
///产品ID(根据开关获取)
|
||
var productIdentifiers:[String] = ["1weekvip","1yearvip","lifetimevip"]
|
||
///产品交易信息组
|
||
private var transactions:[PaymentTransaction] = []
|
||
///产品请求(根据ID获取产品信息)
|
||
private var productsRequest:SKProductsRequest?
|
||
///可用产品组
|
||
private var availableProducts:Set<SKProduct> = []
|
||
///调用方案
|
||
private var showHUD:Bool = false
|
||
///是否是VIP
|
||
var isVIP:Bool {
|
||
//获取VIP过期时间
|
||
guard let lastDate = UserDefaults.standard.object(forKey: "PurchaseVIPDate") as? Date, lastDate.timeIntervalSince(Date()) > 0 else { return false }
|
||
return true
|
||
}
|
||
|
||
override init() {
|
||
super.init()
|
||
|
||
}
|
||
deinit {
|
||
|
||
}
|
||
///校正内购产品交易情况
|
||
func observeVIPStoreKit() {
|
||
//清理原有的交易信息组
|
||
transactions.removeAll()
|
||
//执行交易信息组校正处理
|
||
SwiftyStoreKit.completeTransactions { [weak self] purchases in
|
||
guard let self = self else { return }
|
||
var productId = ""
|
||
//检查交易情况
|
||
purchases.forEach { item in
|
||
switch item.transaction.transactionState {
|
||
case .purchased, .restored://交易完成/处理中
|
||
//检查交易是否需要强制终结
|
||
if item.needsFinishTransaction {
|
||
SwiftyStoreKit.finishTransaction(item.transaction)
|
||
}
|
||
//更新产品ID
|
||
productId = item.productId
|
||
//添加交易信息组
|
||
self.transactions.append(item.transaction)
|
||
case .failed://交易失败
|
||
//检查交易是否需要强制终结
|
||
if item.needsFinishTransaction {
|
||
SwiftyStoreKit.finishTransaction(item.transaction)
|
||
}
|
||
default:
|
||
break
|
||
}
|
||
}
|
||
//交易检索完毕,查看是否有订阅的产品
|
||
if !productId.isEmpty {
|
||
//存在交易产品,重新验证一下流程
|
||
print("当前用户有VIP记录,进行验证")
|
||
self.showHUD = false
|
||
self.purchaseVerificationProductVIP(with: productId)
|
||
}else {
|
||
//无交易内容
|
||
print("当前用户非VIP")
|
||
}
|
||
}
|
||
}
|
||
///获取内购产品信息
|
||
func getVIPAllProducts() {
|
||
let productIds:Set<String> = Set(productIdentifiers)
|
||
SwiftyStoreKit.retrieveProductsInfo(productIds) { [weak self] results in
|
||
guard let self = self else { return }
|
||
//检索产品信息
|
||
if !results.retrievedProducts.isEmpty {
|
||
self.availableProducts = results.retrievedProducts
|
||
}
|
||
}
|
||
}
|
||
|
||
// ///执行产品请求
|
||
// func requestProducts() {
|
||
// guard MP_NetWorkManager.shared.netWorkStatu == .reachable else {
|
||
// //无网络
|
||
// print("无网络状况,无法获得产品")
|
||
// return
|
||
// }
|
||
// //清空可用产品
|
||
// availableProducts = []
|
||
// //将产品ID转为Set集合
|
||
// let setStrings:Set<String> = Set(productIdentifiers)
|
||
// //初始化产品请求
|
||
// productsRequest = SKProductsRequest(productIdentifiers: setStrings)
|
||
// //实现请求代理
|
||
// productsRequest?.delegate = self
|
||
// //开始执行请求
|
||
// productsRequest?.start()
|
||
// }
|
||
///对指定产品进行购买
|
||
func purchaseProduct(with productId:String) {
|
||
showHUD = true
|
||
MP_HUD.loading()
|
||
//判断当前的购买状态是否可以继续执行
|
||
if SwiftyStoreKit.canMakePayments {
|
||
//可以继续购买项目
|
||
MP_AnalyticsManager.shared.VIP_continue_clickAction(productId)
|
||
self.pruchaseProductVIP(with: productId)
|
||
}else {
|
||
//不可以购买项目
|
||
MP_HUD.error("Sorry. Subscription purchase failed", delay: 1.0, completion: nil)
|
||
MP_AnalyticsManager.shared.VIP_buy_failureAction(productId, error: "The current project status is not supported")
|
||
}
|
||
}
|
||
|
||
|
||
///用户重新检索交易
|
||
func restorePurchases() {
|
||
MP_HUD.loading()
|
||
showHUD = true
|
||
self.transactions.removeAll()
|
||
SwiftyStoreKit.restorePurchases(atomically: false) { [weak self] results in
|
||
guard let self = self else { return }
|
||
if !results.restoredPurchases.isEmpty, let productId = results.restoredPurchases.first?.productId {
|
||
//restore成功
|
||
self.transactions.append(contentsOf: results.restoredPurchases.map({$0.transaction}))
|
||
self.purchaseVerificationProductVIP(with: productId)
|
||
return
|
||
}
|
||
if !results.restoreFailedPurchases.isEmpty {
|
||
MP_HUD.error("Restore purchase failed", delay: 1.0, completion: nil)
|
||
return
|
||
}
|
||
MP_HUD.error("No purchase can be restored", delay: 1.0, completion: nil)
|
||
}
|
||
}
|
||
|
||
// ///启动时检索是否购买了广告
|
||
// func startLunchStatus() {
|
||
// //判断是否存在用户收据
|
||
// let receiptURL = Bundle.main.appStoreReceiptURL
|
||
// guard let receipt = try? Data(contentsOf: receiptURL!) else {
|
||
// // 收据不存在
|
||
// print("没有收据,广告默认开")
|
||
// //不能调用AppStore,默认使用广告
|
||
// MP_ADSimpleManager.shared.setOpenAdStatus(true)
|
||
// return
|
||
// }
|
||
// //有收据
|
||
// print("有收据,开始检索本地信息")
|
||
// reloadOpenStatus()
|
||
// }
|
||
//
|
||
|
||
// ///更新广告状态
|
||
// func reloadOpenStatus() {
|
||
// //更新广告开关状态
|
||
// if isProductPurchased(productId: productIdentifiers[0]) || isProductPurchased(productId: productIdentifiers[1]) || isProductPurchased(productId: productIdentifiers[2]){
|
||
// //设置广告开关为关
|
||
// MP_ADSimpleManager.shared.setOpenAdStatus(false)
|
||
// }else {
|
||
// MP_ADSimpleManager.shared.setOpenAdStatus(true)
|
||
// }
|
||
// }
|
||
|
||
}
|
||
//MARK: - 交易执行
|
||
extension MP_IAPManager {
|
||
///执行交易
|
||
private func pruchaseProductVIP(with productId:String) {
|
||
//优先移除交易信息组
|
||
self.transactions.removeAll()
|
||
SwiftyStoreKit.purchaseProduct(productId, quantity: 1, atomically: false) { [weak self] (result) in
|
||
guard let self = self else { return }
|
||
switch result {
|
||
case .success(let product)://订阅成功
|
||
print("VIP Purchase Successfully.")
|
||
//更新订阅信息
|
||
self.transactions.append(product.transaction)
|
||
//检索交易信息
|
||
self.purchaseVerificationProductVIP(with: product.productId)
|
||
case .error(let error)://订阅失败
|
||
MP_HUD.error("Sorry. Subscription purchase failed", delay: 1.0, completion: nil)
|
||
MP_AnalyticsManager.shared.VIP_buy_failureAction(productId, error: "The current project status is not supported")
|
||
print("VIP: \(error.localizedDescription)")
|
||
}
|
||
}
|
||
}
|
||
///交易验证
|
||
private func purchaseVerificationProductVIP(with productId:String) {
|
||
//生成一个验证服务器
|
||
let receiptValidator = AppleReceiptValidator(service: checkIsSandbox() ? .sandbox:.production, sharedSecret: "d29627e4f78b4b50a0ce5166acd8aa9f")
|
||
SwiftyStoreKit.verifyReceipt(using: receiptValidator, forceRefresh: true) { [weak self] result in
|
||
guard let self = self else { return }
|
||
switch result {
|
||
case .success(let receipt)://验证成功
|
||
print("VIP Receipt: \(receipt)")
|
||
//检索是否为终身项目
|
||
if productId == "lifetimevip" {
|
||
//终身项目
|
||
let purchaseResult = SwiftyStoreKit.verifyPurchase(productId: productId, inReceipt: receipt)
|
||
switch purchaseResult {
|
||
case .purchased(let item):
|
||
print("VIP LifeTime verifyPurchase successed current item: \(item)")
|
||
//到期时间一百年后
|
||
let lifeDate = Date().addingTimeInterval(60 * 60 * 24 * 365 * 100)
|
||
//本地记录VIP到期时间
|
||
UserDefaults.standard.set(lifeDate, forKey: "PurchaseVIPDate")
|
||
UserDefaults.standard.synchronize()
|
||
//结算交易信息
|
||
self.showSuccessfullyHUD()
|
||
self.finishedAllTransactionsVIP()
|
||
case .notPurchased://订阅失败
|
||
self.finishedAllTransactionsVIP()
|
||
self.showFailedHUD()
|
||
}
|
||
}else {
|
||
//非终身项目,要计算项目时间
|
||
let purchaseResult = SwiftyStoreKit.verifySubscription(ofType: .autoRenewable, productId: productId, inReceipt: receipt)
|
||
switch purchaseResult {
|
||
case .purchased(let expiryDate, let items):
|
||
print("VIP \(productId) valid until \(expiryDate)")
|
||
UserDefaults.standard.set(expiryDate, forKey: "PurchaseVIPDate")
|
||
UserDefaults.standard.synchronize()
|
||
//结算交易信息
|
||
self.showSuccessfullyHUD()
|
||
self.finishedAllTransactionsVIP()
|
||
case .expired(let expiryDate, let items)://过期了
|
||
self.finishedAllTransactionsVIP()
|
||
self.showFailedHUD()
|
||
case .notPurchased://订阅失败
|
||
self.finishedAllTransactionsVIP()
|
||
self.showFailedHUD()
|
||
}
|
||
}
|
||
case .error(let error)://验证失败
|
||
self.finishedAllTransactionsVIP()
|
||
self.showFailedHUD()
|
||
}
|
||
}
|
||
}
|
||
//展示交易成功
|
||
private func showSuccessfullyHUD() {
|
||
if showHUD == true {
|
||
MP_HUD.success("Successfully purchased", delay: 1.5, completion: nil)
|
||
}
|
||
}
|
||
//展示交易失败
|
||
private func showFailedHUD() {
|
||
if showHUD == true {
|
||
MP_HUD.error("Failed purchased", delay: 1.5, completion: nil)
|
||
}
|
||
}
|
||
|
||
//结算交易信息组
|
||
private func finishedAllTransactionsVIP() {
|
||
self.transactions.forEach { item in
|
||
SwiftyStoreKit.finishTransaction(item)
|
||
}
|
||
self.transactions.removeAll()
|
||
}
|
||
|
||
private func checkIsSandbox() -> Bool {
|
||
var isSandbox = false
|
||
#if DEBUG
|
||
isSandbox = true
|
||
#endif
|
||
return isSandbox
|
||
}
|
||
}
|
||
|
||
//MARK: - SKProductsRequestDelegate
|
||
//extension MP_IAPManager: SKProductsRequestDelegate, SKPaymentTransactionObserver {
|
||
// ///产品请求回调执行
|
||
// func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
|
||
// //获得可用产品
|
||
// availableProducts = response.products
|
||
// //判空
|
||
// guard availableProducts.isEmpty == false else {
|
||
// //无可用产品
|
||
// print("无可用产品")
|
||
// return
|
||
// }
|
||
// //产品可用,检索产品内容
|
||
// for (index, item) in availableProducts.enumerated() {
|
||
// print("第\(index)号产品--\(item)")
|
||
// }
|
||
// }
|
||
// ///产品请求失败
|
||
// func request(_ request: SKRequest, didFailWithError error: any Error) {
|
||
// print("Failed to fetch products: \(error.localizedDescription)")
|
||
// }
|
||
// ///当交易的状况发生变化后
|
||
// func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
|
||
// //遍历交易项
|
||
// transactions.forEach { item in
|
||
// //检索每笔交易的情况
|
||
// switch item.transactionState {
|
||
// case .purchasing://交易中
|
||
// break
|
||
// case .purchased://用户已经付款了
|
||
// complete(transaction: item)
|
||
// case .failed://交易失败了
|
||
// fail(transaction: item)
|
||
// case .restored://交易重置
|
||
// restore(transaction: item)
|
||
// case .deferred://等待外部操作
|
||
// break
|
||
// @unknown default:
|
||
// break
|
||
// }
|
||
// }
|
||
// }
|
||
// ///重置交易状况
|
||
// func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
|
||
// print("已重启交易")
|
||
// MP_HUD.hideNow()
|
||
// //确定用户是否购买过订单
|
||
// if queue.transactions.isEmpty {
|
||
// //用户ID没有购买过任何与当前产品有关的交易
|
||
// print("没有购买产品/交易已过期")
|
||
// productIdentifiers.forEach { item in
|
||
// cleanPurchase(productId: item)
|
||
// }
|
||
// MP_ADSimpleManager.shared.setOpenAdStatus(true)
|
||
// }
|
||
// }
|
||
// func paymentQueue(_ queue: SKPaymentQueue, restoreCompletedTransactionsFailedWithError error: any Error) {
|
||
// print("重启交易失败")
|
||
// MP_HUD.error("The current transaction failed".localizableString(), delay: 1.0, completion: nil)
|
||
// //清理所有的VIP信息
|
||
// productIdentifiers.forEach { item in
|
||
// cleanPurchase(productId: item)
|
||
// }
|
||
// MP_ADSimpleManager.shared.setOpenAdStatus(true)
|
||
// }
|
||
// //存入交易信息值
|
||
// private func storePurchase(productId: String) {
|
||
// if isProductPurchased(productId: productId) == false {
|
||
// var purchasedProducts = UserDefaults.standard.array(forKey: "PurchasedProducts") as? [String] ?? []
|
||
// purchasedProducts.append(productId)
|
||
// UserDefaults.standard.set(purchasedProducts, forKey: "PurchasedProducts")
|
||
// }
|
||
// }
|
||
// //检索用户是否购买该产品VIP产品
|
||
// private func isProductPurchased(productId: String) -> Bool {
|
||
// let purchasedProducts = UserDefaults.standard.array(forKey: "PurchasedProducts") as? [String] ?? []
|
||
// return purchasedProducts.contains(productId)
|
||
// }
|
||
// //清理对应的广告ID
|
||
// private func cleanPurchase(productId: String) {
|
||
// if isProductPurchased(productId: productId) {
|
||
// var purchasedProducts = UserDefaults.standard.array(forKey: "PurchasedProducts") as? [String] ?? []
|
||
// purchasedProducts.removeAll(where: {$0 == productId})
|
||
// UserDefaults.standard.set(purchasedProducts, forKey: "PurchasedProducts")
|
||
// }
|
||
// }
|
||
// ///交易完成
|
||
// private func complete(transaction: SKPaymentTransaction) {
|
||
// MP_HUD.success("Successfully purchased", delay: 1.0, completion: nil)
|
||
// print("Transaction completed successfully.")
|
||
// MP_AnalyticsManager.shared.VIP_buy_successAction(transaction.payment.productIdentifier)
|
||
// validateReceipt { [weak self] status in
|
||
// guard let self = self else {return}
|
||
// if status {
|
||
// // 存储购买信息
|
||
// self.storePurchase(productId: transaction.payment.productIdentifier)
|
||
// SKPaymentQueue.default().finishTransaction(transaction)
|
||
// //更新广告开关状态
|
||
// reloadOpenStatus()
|
||
// }
|
||
// }
|
||
// }
|
||
// //重启交易
|
||
// private func restore(transaction: SKPaymentTransaction) {
|
||
// print("Transaction restored.")
|
||
// validateReceipt { [weak self] status in
|
||
// guard let self = self else {return}
|
||
// if status {
|
||
// // 存储购买信息
|
||
// self.storePurchase(productId: transaction.payment.productIdentifier)
|
||
// SKPaymentQueue.default().finishTransaction(transaction)
|
||
// //更新广告开关状态
|
||
// reloadOpenStatus()
|
||
// }
|
||
// }
|
||
// }
|
||
// ///交易失败
|
||
// private func fail(transaction: SKPaymentTransaction) {
|
||
// //检索错误
|
||
// if let error = transaction.error as NSError? {
|
||
// MP_AnalyticsManager.shared.VIP_buy_failureAction(transaction.payment.productIdentifier, error: error.localizedDescription)
|
||
// if error.code != SKError.paymentCancelled.rawValue {
|
||
// MP_HUD.error("The current transaction failed".localizableString(), delay: 1.0, completion: nil)
|
||
// } else {
|
||
// MP_HUD.onlytext("The current transaction has been canceled".localizableString(), delay: 1.0, completion: nil)
|
||
// }
|
||
// }
|
||
// }
|
||
// //获取收据信息
|
||
// func fetchReceipt() -> String? {
|
||
// guard let receiptURL = Bundle.main.appStoreReceiptURL else { return nil }
|
||
// guard let receiptData = try? Data(contentsOf: receiptURL) else { return nil }
|
||
// return receiptData.base64EncodedString(options: [])
|
||
// }
|
||
// //验证收据信息
|
||
// func validateReceipt(completion: @escaping (Bool) -> Void) {
|
||
// guard MP_NetWorkManager.shared.netWorkStatu == .reachable else {
|
||
// completion(false)
|
||
// return
|
||
// }
|
||
// //获取收据信息
|
||
// guard let receiptString = fetchReceipt() else {
|
||
// completion(false)
|
||
// return
|
||
// }
|
||
// //生成请求参数
|
||
// let requestDictionary = ["receipt-data": receiptString,
|
||
// "password": "d29627e4f78b4b50a0ce5166acd8aa9f" ]
|
||
// guard JSONSerialization.isValidJSONObject(requestDictionary) else {
|
||
// completion(false)
|
||
// return
|
||
// }
|
||
//
|
||
// do {
|
||
// let requestData = try JSONSerialization.data(withJSONObject: requestDictionary)
|
||
// #if DEBUG
|
||
// let validationURLString = "https://sandbox.itunes.apple.com/verifyReceipt"
|
||
// #else
|
||
// let validationURLString = "https://buy.itunes.apple.com/verifyReceipt"
|
||
// #endif
|
||
// guard let validationURL = URL(string: validationURLString) else {
|
||
// completion(false)
|
||
// return
|
||
// }
|
||
// //创建请求
|
||
// var request = URLRequest(url: validationURL)
|
||
// request.httpMethod = "POST"
|
||
// request.cachePolicy = .reloadIgnoringCacheData
|
||
// request.httpBody = requestData
|
||
// //设置会话
|
||
// let session = URLSession.shared
|
||
// //执行任务
|
||
// let task = session.dataTask(with: request) { data, response, error in
|
||
// guard error == nil, let data = data else {
|
||
// completion(false)
|
||
// return
|
||
// }
|
||
//
|
||
// do {
|
||
// if let jsonResponse = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String: Any] {
|
||
// if let status = jsonResponse["status"] as? Int {
|
||
// if status == 0 {
|
||
// completion(true)
|
||
// } else if status == 21007 {
|
||
// self.validateReceiptInSandbox(receiptString: receiptString, completion: completion)
|
||
// } else {
|
||
// print("Receipt validation failed with status: \(status)")
|
||
// completion(false)
|
||
// }
|
||
// } else {
|
||
// completion(false)
|
||
// }
|
||
// } else {
|
||
// completion(false)
|
||
// }
|
||
// } catch {
|
||
// completion(false)
|
||
// }
|
||
// }
|
||
// task.resume()
|
||
// } catch {
|
||
// completion(false)
|
||
// }
|
||
// }
|
||
// func validateReceiptInSandbox(receiptString: String, completion: @escaping (Bool) -> Void) {
|
||
// let requestDictionary = ["receipt-data": receiptString,
|
||
// "password": "d29627e4f78b4b50a0ce5166acd8aa9f"]
|
||
// guard JSONSerialization.isValidJSONObject(requestDictionary) else {
|
||
// completion(false)
|
||
// return
|
||
// }
|
||
//
|
||
// do {
|
||
// let requestData = try JSONSerialization.data(withJSONObject: requestDictionary)
|
||
// let validationURLString = "https://sandbox.itunes.apple.com/verifyReceipt"
|
||
// guard let validationURL = URL(string: validationURLString) else {
|
||
// completion(false)
|
||
// return
|
||
// }
|
||
//
|
||
// var request = URLRequest(url: validationURL)
|
||
// request.httpMethod = "POST"
|
||
// request.cachePolicy = .reloadIgnoringCacheData
|
||
// request.httpBody = requestData
|
||
//
|
||
// let session = URLSession.shared
|
||
// let task = session.dataTask(with: request) { data, response, error in
|
||
// guard error == nil, let data = data else {
|
||
// completion(false)
|
||
// return
|
||
// }
|
||
// do {
|
||
// if let jsonResponse = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String: Any] {
|
||
// if let status = jsonResponse["status"] as? Int, status == 0 {
|
||
// completion(true)
|
||
// } else {
|
||
// completion(false)
|
||
// }
|
||
// } else {
|
||
// completion(false)
|
||
// }
|
||
// } catch {
|
||
// completion(false)
|
||
// }
|
||
// }
|
||
// task.resume()
|
||
// } catch {
|
||
// completion(false)
|
||
// }
|
||
// }
|
||
//}
|