363 lines
14 KiB
Swift
363 lines
14 KiB
Swift
//
|
||
// MP_IAPManager.swift
|
||
// relax.offline.mp3.music
|
||
//
|
||
// Created by Mr.Zhou on 2024/7/29.
|
||
//
|
||
|
||
import UIKit
|
||
import StoreKit
|
||
///内部购买项目管理器
|
||
class MP_IAPManager: NSObject {
|
||
///单例
|
||
static let shared = MP_IAPManager()
|
||
///产品ID(根据开关获取)
|
||
private var productIdentifiers:[String] = ["1weekvip","1yearvip","lifetimevip"]
|
||
///产品请求(根据ID获取产品信息)
|
||
private var productsRequest:SKProductsRequest?
|
||
///可用产品组
|
||
private var availableProducts:[SKProduct] = []
|
||
|
||
override init() {
|
||
super.init()
|
||
SKPaymentQueue.default().add(self)
|
||
}
|
||
deinit {
|
||
SKPaymentQueue.default().remove(self)
|
||
}
|
||
///执行产品请求
|
||
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(_ index:Int) {
|
||
//检索用户选择的产品
|
||
guard availableProducts.indices.contains(index) else {
|
||
//越界了,说明广告总数出了问题,重新请求
|
||
MP_HUD.onlytext("Try again".localizableString(), delay: 1.0, completion: nil)
|
||
MP_AnalyticsManager.shared.VIP_buy_failureAction(productIdentifiers[index], error: "Product data loading failed")
|
||
requestProducts()
|
||
return
|
||
}
|
||
let id = productIdentifiers[index]
|
||
if let product = availableProducts.first(where: {$0.productIdentifier == id}) {
|
||
MP_AnalyticsManager.shared.VIP_continue_clickAction(id)
|
||
//将产品转为交易项
|
||
let payment = SKPayment(product: product)
|
||
//将交易项添加进购买线程中
|
||
SKPaymentQueue.default().add(payment)
|
||
MP_HUD.loading()
|
||
}
|
||
}
|
||
///用户重新检索交易
|
||
func restorePurchases() {
|
||
MP_HUD.loading()
|
||
//更新信息
|
||
SKPaymentQueue.default().restoreCompletedTransactions()
|
||
}
|
||
///系统自动检索
|
||
func systemRestorePurchases() {
|
||
let receiptURL = Bundle.main.appStoreReceiptURL
|
||
guard let receipt = try? Data(contentsOf: receiptURL!) else {
|
||
// 收据不存在
|
||
print("没有收据,广告默认开")
|
||
//不能调用AppStore,默认使用广告
|
||
MP_ADSimpleManager.shared.setOpenAdStatus(true)
|
||
return
|
||
}
|
||
//收据,重置交易记录
|
||
SKPaymentQueue.default().restoreCompletedTransactions()
|
||
}
|
||
///启动时检索是否购买了广告
|
||
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: - 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)
|
||
}
|
||
}
|
||
}
|