ReaderHive/ReaderHive/Libs/IAP/NRIapManager.swift
2026-01-27 15:55:28 +08:00

293 lines
11 KiB
Swift

//
// NRIapManager.swift
// ReaderHive
//
// Created by on 2025/12/10.
//
import UIKit
internal import StoreKit
import JXIAPManager
class NRIapManager {
typealias CompletionHandler = ((_ finish: Bool) -> Void)
///
static let IAPPrefix = "readerhive"
static let manager = NRIapManager()
///
private var completionHandler: CompletionHandler?
private var shortPlayId: String?
private var videoId: String?
private lazy var iapManager: JXIAPManager = {
let manager = JXIAPManager.manager
manager.delegate = self
return manager
}()
private var orderCode: String?
private var payId: String?
///
private var payRequest: NRPayDataRequest?
var payDateModel: NRPayDateModel?
///使
///
private var waitRestoreModel: NRWaitRestoreModel? = UserDefaults.nr_object(forKey: kNRWaitRestoreIAPDefaultsKey, as: NRWaitRestoreModel.self)
///
func start(model: NRPayItem, shortPlayId: String? = nil, videoId: String? = nil, hudShowView: UIView? = nil, handler: CompletionHandler? = nil) {
if let _ = self.waitRestoreModel {
NRToast.show(text: "pay_error_2".localized)
handler?(false)
return
}
guard let payId = model.id else {
handler?(false)
return
}
self.shortPlayId = shortPlayId
self.videoId = videoId
self.completionHandler = handler
self.waitRestoreModel = NRWaitRestoreModel()
self.waitRestoreModel?.buyType = model.buy_type
let productId = getProductId(templateId: model.ios_template_id) ?? ""
var isDiscount = false
var identifierDiscount: String? = nil
if model.discount_type == 1, let _ = model.introductionaryOffer {
isDiscount = true
} else if model.discount_type == 2, let discount = model.promotionalOffers?.first {
isDiscount = true
identifierDiscount = discount.identifier
}
NRHud.show(containerView: hudShowView)
NRStoreAPI.requestCreateOrder(payId: payId, shortPlayId: shortPlayId ?? "0", videoId: videoId ?? "0", isDiscount: isDiscount, identifierDiscount: identifierDiscount) { orderModel in
guard let orderModel = orderModel else {
NRHud.dismiss()
self.waitRestoreModel = nil
self.completionHandler?(false)
self.clean()
return
}
self.orderCode = orderModel.order_code
self.payId = payId
self.waitRestoreModel?.payId = payId
self.waitRestoreModel?.orderCode = orderModel.order_code
var discount: SKPaymentDiscount? = nil
if let identifierDiscount = identifierDiscount,
let signData = orderModel.discount?.sign_data,
let keyIdentifier = signData.keyIdentifier,
let nonce = UUID(uuidString: signData.nonce ?? ""),
let signature = signData.signature,
let timestamp = signData.timestamp
{
discount = SKPaymentDiscount(identifier: identifierDiscount,
keyIdentifier: keyIdentifier,
nonce: nonce,
signature: signature,
timestamp: NSNumber(value: timestamp))
}
self.iapManager.start(productId: productId, orderId: self.orderCode ?? "", applicationUsername: orderModel.discount?.sign_data?.applicationUsername, discount: discount)
}
}
func restore(isLoding: Bool = true, shortPlayId: String? = nil, videoId: String? = nil, completer: ((_ isFinish: Bool, _ buyType: NRStoreAPI.BuyType?) -> Void)?) {
let buyType = self.waitRestoreModel?.buyType
guard let waitRestoreModel = self.waitRestoreModel,
let orderCode = waitRestoreModel.orderCode,
let payId = waitRestoreModel.payId,
let receipt = waitRestoreModel.receipt,
let transactionId = waitRestoreModel.transactionId
else {
if isLoding {
NRToast.show(text: "pay_error_3".localized)
}
completer?(false, buyType)
return
}
if isLoding {
NRHud.show()
}
let verifyData = self.getVerifyOrderParameters(orderCode: orderCode, payId: payId, transactionId: transactionId, purchaseToken: receipt)
let statParamenters: [String : Any] = [
"type" : isLoding ? "manual" : "auto",
"pay_data" : verifyData.toJsonString() ?? ""
]
NRStatAPI.nr_requestEventStat(orderCode: orderCode, shortPlayId: shortPlayId, videoId: videoId, eventKey: .payRestore, errorMsg: "restore", otherParamenters: statParamenters)
NRStoreAPI.requestVerifyOrder(parameters: verifyData) { response in
if isLoding {
NRHud.dismiss()
}
guard let model = response.data else {
completer?(false, buyType)
return
}
self.waitRestoreModel = nil
UserDefaults.nr_setObject(nil, forKey: kNRWaitRestoreIAPDefaultsKey)
if model.status == "success" {
if buyType == .subVip {
NRLoginManager.manager.userInfo?.is_vip = true
}
if isLoding {
NRToast.show(text: "reader_success".localized)
}
completer?(true, buyType)
if buyType == .subVip {
NotificationCenter.default.post(name: NRIapManager.buyVipFinishNotification, object: nil)
}
} else {
completer?(false, buyType)
}
}
}
func getProductId(templateId: String?) -> String? {
guard let templateId = templateId else { return nil }
return NRIapManager.IAPPrefix + "." + templateId
}
func clean() {
self.orderCode = nil
self.payId = nil
self.shortPlayId = nil
self.videoId = nil
self.completionHandler = nil
}
}
//MARK: JXIAPManagerDelegate
extension NRIapManager: JXIAPManagerDelegate {
func jx_iapPaySuccess(productId: String, receipt: String, transactionIdentifier: String) {
guard let orderCode = self.orderCode, let payId = self.payId else {
self.waitRestoreModel = nil
self.completionHandler?(false)
self.clean()
NRHud.dismiss()
return
}
self.waitRestoreModel?.productId = productId
self.waitRestoreModel?.receipt = receipt
self.waitRestoreModel?.transactionId = transactionIdentifier
UserDefaults.nr_setObject(self.waitRestoreModel, forKey: kNRWaitRestoreIAPDefaultsKey)
#if DEBUG
let verifyData = self.getVerifyOrderParameters(orderCode: orderCode, payId: payId, transactionId: transactionIdentifier, purchaseToken: receipt)
#else
let verifyData = self.getVerifyOrderParameters(orderCode: orderCode, payId: payId, transactionId: transactionIdentifier, purchaseToken: receipt)
#endif
NRStoreAPI.requestVerifyOrder(parameters: verifyData) { response in
NRHud.dismiss()
guard let model = response.data else {
NRStatAPI.nr_requestEventStat(orderCode: self.orderCode, shortPlayId: self.shortPlayId, videoId: self.videoId, eventKey: .payCallback, errorMsg: verifyData.toJsonString())
self.completionHandler?(false)
self.clean()
return
}
let buyType = self.waitRestoreModel?.buyType
self.waitRestoreModel = nil
UserDefaults.nr_setObject(nil, forKey: kNRWaitRestoreIAPDefaultsKey)
if model.status == "success" {
if buyType == .subVip {
NRLoginManager.manager.userInfo?.is_vip = true
}
NRToast.show(text: "reader_success".localized)
self.completionHandler?(true)
if buyType == .subVip {
NotificationCenter.default.post(name: NRIapManager.buyVipFinishNotification, object: nil)
}
} else {
NRToast.show(text: "pay_error_4".localized)
NRStatAPI.nr_requestEventStat(orderCode: self.orderCode, shortPlayId: self.shortPlayId, videoId: self.videoId, eventKey: .payCallback, errorMsg: verifyData.toJsonString())
self.completionHandler?(false)
}
self.clean()
}
}
func jx_iapPayFailed(productId: String, code: JXIAPManager.ErrorCode, msg: String?) {
NRHud.dismiss()
if code == .noProduct {
NRToast.show(text: "pay_error_5".localized)
} else if code == .cancelled {
NRToast.show(text: "pay_error_6".localized)
}
if code == .cancelled {
NRStatAPI.nr_requestEventStat(orderCode: self.orderCode, shortPlayId: self.shortPlayId, videoId: self.videoId, eventKey: .payCancel, errorMsg: "user cancel")
} else {
NRStatAPI.nr_requestEventStat(orderCode: self.orderCode, shortPlayId: self.shortPlayId, videoId: self.videoId, eventKey: .payError, errorMsg: msg)
}
self.completionHandler?(false)
self.waitRestoreModel = nil
self.clean()
}
}
extension NRIapManager {
func getVerifyOrderParameters(orderCode: String, payId: String, transactionId: String, purchaseToken: String) -> [String : Any] {
let parameters: [String : Any] = [
"order_code" : orderCode,
"pay_setting_id" : payId,
"pkg_name" : kNRAPPBundleIdentifier,
"transaction_id": transactionId,
"purchases_token" : purchaseToken
]
return parameters
}
///
func preloadingProducts() {
JXIAPManager.manager.fetchReceipt { _ in
self.payRequest = NRPayDataRequest()
self.payRequest?.requestProducts(isLoding: false, isToast: false) { _ in
}
}
}
}
extension NRIapManager {
///
@objc static let buyVipFinishNotification = Notification.Name(rawValue: "NRIapManager.buyVipFinishNotification")
}