diff --git a/Veloria.xcodeproj/project.pbxproj b/Veloria.xcodeproj/project.pbxproj index e2d2ed9..c02672f 100644 --- a/Veloria.xcodeproj/project.pbxproj +++ b/Veloria.xcodeproj/project.pbxproj @@ -208,6 +208,11 @@ BFF5B25A2DF13BE50044227A /* VPGiveCoinRecordsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF5B2592DF13BE50044227A /* VPGiveCoinRecordsCell.swift */; }; BFF5B25C2DF13F850044227A /* Date+VPAdd.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF5B25B2DF13F850044227A /* Date+VPAdd.swift */; }; BFF5B25E2DF1423F0044227A /* VPWalletBaseCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF5B25D2DF1423F0044227A /* VPWalletBaseCell.swift */; }; + BFF5B2612DF16B430044227A /* JXIAPManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF5B25F2DF16B430044227A /* JXIAPManager.swift */; }; + BFF5B2642DF16C380044227A /* VPIAPManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF5B2632DF16C380044227A /* VPIAPManager.swift */; }; + BFF5B2662DF16CF60044227A /* VPWaitRestoreModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF5B2652DF16CF60044227A /* VPWaitRestoreModel.swift */; }; + BFF5B2682DF16EA30044227A /* VPIAPOrderModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF5B2672DF16EA30044227A /* VPIAPOrderModel.swift */; }; + BFF5B26A2DF170DD0044227A /* VPIAPVerifyModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF5B2692DF170DD0044227A /* VPIAPVerifyModel.swift */; }; F939C04AD4003BA127F15C28 /* Pods_Veloria.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 34F57E87E765BF8D72A43DCA /* Pods_Veloria.framework */; }; /* End PBXBuildFile section */ @@ -421,6 +426,11 @@ BFF5B2592DF13BE50044227A /* VPGiveCoinRecordsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPGiveCoinRecordsCell.swift; sourceTree = ""; }; BFF5B25B2DF13F850044227A /* Date+VPAdd.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+VPAdd.swift"; sourceTree = ""; }; BFF5B25D2DF1423F0044227A /* VPWalletBaseCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPWalletBaseCell.swift; sourceTree = ""; }; + BFF5B25F2DF16B430044227A /* JXIAPManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JXIAPManager.swift; sourceTree = ""; }; + BFF5B2632DF16C380044227A /* VPIAPManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPIAPManager.swift; sourceTree = ""; }; + BFF5B2652DF16CF60044227A /* VPWaitRestoreModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPWaitRestoreModel.swift; sourceTree = ""; }; + BFF5B2672DF16EA30044227A /* VPIAPOrderModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPIAPOrderModel.swift; sourceTree = ""; }; + BFF5B2692DF170DD0044227A /* VPIAPVerifyModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPIAPVerifyModel.swift; sourceTree = ""; }; E0BDA3570E00C90877E45AA0 /* Pods-VideoPlayer.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-VideoPlayer.debug.xcconfig"; path = "Target Support Files/Pods-VideoPlayer/Pods-VideoPlayer.debug.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -498,6 +508,7 @@ 1B056E352DDAC1DE007EE38D /* Libs */ = { isa = PBXGroup; children = ( + BFF5B2622DF16BE10044227A /* VPIAPManager */, BFF5B2352DF013020044227A /* Alert */, BF5E75D92DE5B89300DE9DFE /* MarqueeView */, BF5E75B42DE46D9500DE9DFE /* Empty */, @@ -715,6 +726,7 @@ BF0FA6E82DDC5F6F00C9E5F2 /* Thirdparty */ = { isa = PBXGroup; children = ( + BFF5B2602DF16B430044227A /* JXIAPManager */, BF5E75CA2DE5692D00DE9DFE /* JXTransition */, BF0FA7932DE16E9300C9E5F2 /* JXTagView */, BF0FA6ED2DDC5F8700C9E5F2 /* JXUUID */, @@ -1174,6 +1186,25 @@ path = Alert; sourceTree = ""; }; + BFF5B2602DF16B430044227A /* JXIAPManager */ = { + isa = PBXGroup; + children = ( + BFF5B25F2DF16B430044227A /* JXIAPManager.swift */, + ); + path = JXIAPManager; + sourceTree = ""; + }; + BFF5B2622DF16BE10044227A /* VPIAPManager */ = { + isa = PBXGroup; + children = ( + BFF5B2632DF16C380044227A /* VPIAPManager.swift */, + BFF5B2652DF16CF60044227A /* VPWaitRestoreModel.swift */, + BFF5B2672DF16EA30044227A /* VPIAPOrderModel.swift */, + BFF5B2692DF170DD0044227A /* VPIAPVerifyModel.swift */, + ); + path = VPIAPManager; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -1321,6 +1352,7 @@ BF0FA6DC2DDC5CD700C9E5F2 /* VPTokenModel.swift in Sources */, BF0FA7572DDF159B00C9E5F2 /* VPExploreViewController.swift in Sources */, 1B056E772DDB3641007EE38D /* VPTabBarItemNormalVew.swift in Sources */, + BFF5B2662DF16CF60044227A /* VPWaitRestoreModel.swift in Sources */, 1B056E4B2DDAC6BA007EE38D /* VPDefine.swift in Sources */, BFF5B2302DEFEF0C0044227A /* VPDeleteAccountNormalView.swift in Sources */, 1B056E702DDB019B007EE38D /* VPTabBarItemContainer.swift in Sources */, @@ -1359,6 +1391,7 @@ BFF5AFA42DE6F15E0044227A /* VPMeVipCell.swift in Sources */, BFF5AFB02DE7F9A80044227A /* VPVipViewController.swift in Sources */, BF0FA75D2DDF208400C9E5F2 /* VPExplorePlayerControlView.swift in Sources */, + BFF5B2682DF16EA30044227A /* VPIAPOrderModel.swift in Sources */, BFF5B21C2DEEDE130044227A /* VPWebViewController+Script.swift in Sources */, BF5E75CB2DE5692D00DE9DFE /* UINavigationController+JXTransition.swift in Sources */, BF5E75CC2DE5692D00DE9DFE /* JXTransitionDefine.swift in Sources */, @@ -1429,6 +1462,7 @@ BFF5AFA82DE704DC0044227A /* VPMeCoinCell.swift in Sources */, BFF5AFAE2DE717BB0044227A /* VPVipPageViewController.swift in Sources */, BFF5B2502DF12FBC0044227A /* VPConsumptionRecordsViewController.swift in Sources */, + BFF5B2642DF16C380044227A /* VPIAPManager.swift in Sources */, BF0FA70E2DDC6ACC00C9E5F2 /* VPHomeItemContentCell.swift in Sources */, BF0FA6D72DDC5BE100C9E5F2 /* VPURLPath.swift in Sources */, 1B056E5B2DDACD80007EE38D /* UIColor+VPAdd.swift in Sources */, @@ -1470,11 +1504,13 @@ BFF5AFAC2DE70CE20044227A /* VPMeToolCell.swift in Sources */, BF5E75B32DE465EC00DE9DFE /* Dictionary+SPAdd.swift in Sources */, BF0FA7162DDC78FF00C9E5F2 /* ZKCycleScrollViewFlowLayout.swift in Sources */, + BFF5B2612DF16B430044227A /* JXIAPManager.swift in Sources */, BF0FA7172DDC78FF00C9E5F2 /* ZKCycleScrollView.swift in Sources */, BF0FA7612DDFFE7100C9E5F2 /* VPVideoDetailModel.swift in Sources */, BFF5AFD22DE9A58A0044227A /* VPVIPRecordViewController.swift in Sources */, BFF5AFDA2DEE90350044227A /* VPVideoLockView.swift in Sources */, BF5E75B82DE46F7100DE9DFE /* VPNetworkReachabilityManager.swift in Sources */, + BFF5B26A2DF170DD0044227A /* VPIAPVerifyModel.swift in Sources */, BF0FA6D52DDC5B5D00C9E5F2 /* VPApi.swift in Sources */, BF0FA7C12DE45D5D00C9E5F2 /* VPUserInfo.swift in Sources */, BF0FA7392DDECF8900C9E5F2 /* VPHomeListViewController.swift in Sources */, diff --git a/Veloria/AppDelegate/AppDelegate+Config.swift b/Veloria/AppDelegate/AppDelegate+Config.swift index 2481478..82b5e96 100644 --- a/Veloria/AppDelegate/AppDelegate+Config.swift +++ b/Veloria/AppDelegate/AppDelegate+Config.swift @@ -14,6 +14,27 @@ extension AppDelegate { UIView.vp_Awake() VPToast.config() + + congifNavigation() + } + +} + +extension AppDelegate { + + private func congifNavigation() { + + let barButtonItem = UIBarButtonItem.appearance() + + barButtonItem.setTitleTextAttributes([ + .foregroundColor : UIColor.colorFFFFFF(), + ], for: .normal) + + barButtonItem.setTitleTextAttributes([ + .foregroundColor : UIColor.colorFFFFFF() + ], for: .highlighted) + + } } diff --git a/Veloria/AppDelegate/AppDelegate.swift b/Veloria/AppDelegate/AppDelegate.swift index 2d9bb3c..7b7208a 100644 --- a/Veloria/AppDelegate/AppDelegate.swift +++ b/Veloria/AppDelegate/AppDelegate.swift @@ -21,6 +21,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate { VPLoginManager.manager.updateUserInfo(completer: nil) + let _ = JXIAPManager.manager + self.registThirdparty(application, didFinishLaunchingWithOptions: launchOptions) return true diff --git a/Veloria/Base/Define/VPUserDefaultsKey.swift b/Veloria/Base/Define/VPUserDefaultsKey.swift index bbfb6b8..8525384 100644 --- a/Veloria/Base/Define/VPUserDefaultsKey.swift +++ b/Veloria/Base/Define/VPUserDefaultsKey.swift @@ -15,3 +15,6 @@ let kVPLoginTokenDefaultsKey = "kVPLoginTokenDefaultsKey" ///用户信息 let kVPLoginUserInfoDefaultsKey = "kSPLoginUserInfoDefaultsKey" + +///待恢复数据 +let kVPWaitRestoreIAPDefaultsKey = "kVPWaitRestoreIAPDefaultsKey" diff --git a/Veloria/Base/Networking/API/VPWalletAPI.swift b/Veloria/Base/Networking/API/VPWalletAPI.swift index 9ca9e2f..08a22f8 100644 --- a/Veloria/Base/Networking/API/VPWalletAPI.swift +++ b/Veloria/Base/Networking/API/VPWalletAPI.swift @@ -20,6 +20,21 @@ class VPWalletAPI { param.method = .get VPNetwork.request(parameters: param) { (response: VPNetworkResponse) in + /* + if let data = response.data { + var vipList: [VPPayTemplateItem] = [] + data.list_sub_vip?.forEach({ + if $0.vip_type_key == .quarter { + vipList.append($0) + } + }) + data.list_sub_vip = vipList + completer?(data) + + } else { + completer?(nil) + } + */ completer?(response.data) } } @@ -80,4 +95,46 @@ class VPWalletAPI { completer?(response.data) } } + + ///创建内购订单 + static func requestCreateOrder(payId: String, shortPlayId: String, videoId: String, completer: ((_ orderModel: VPIAPOrderModel?) -> Void)?) { + var param = VPNetworkParameters(path: "/createOrder") + param.isToast = false + param.parameters = [ + "payment_channel" : "apple", + "short_play_id" : shortPlayId, + "video_id" : videoId, + "pay_setting_id" : payId + ] + + VPNetwork.request(parameters: param) { (response: VPNetworkResponse) in + if let message = response.data?.message, message.count > 0 { + if response.data?.code == 30007 { + VPToast.show(text: "kVipToast01".localized) + } else { + VPToast.show(text: message) + } + + completer?(nil) + } else { + completer?(response.data) + } + } + } + + ///校验内购 + static func requestVerifyOrder(orderCode: String, payId: String, productId: String, purchaseToken: String, completer: ((_ model: VPIAPVerifyModel?) -> Void)?) { + var param = VPNetworkParameters(path: "/applePaid") + param.parameters = [ + "order_code" : orderCode, + "pay_setting_id" : payId, + "pkg_name" : kVPAPPBundleIdentifier, + "transaction_id" : productId, + "purchases_token" : purchaseToken + ] + + VPNetwork.request(parameters: param) { (response: VPNetworkResponse) in + completer?(response.data) + } + } } diff --git a/Veloria/Class/Player/Controller/VPDetailPlayerViewController.swift b/Veloria/Class/Player/Controller/VPDetailPlayerViewController.swift index 0ae7473..c2513ef 100644 --- a/Veloria/Class/Player/Controller/VPDetailPlayerViewController.swift +++ b/Veloria/Class/Player/Controller/VPDetailPlayerViewController.swift @@ -40,10 +40,16 @@ class VPDetailPlayerViewController: VPVideoPlayerViewController { ///选集 private weak var episodeView: VPEpisodeView? + deinit { + NotificationCenter.default.removeObserver(self) + } + override func viewDidLoad() { super.viewDidLoad() self.delegate = self self.dataSource = self + NotificationCenter.default.addObserver(self, selector: #selector(buyVipFinishNotification), name: VPIAPManager.buyVipFinishNotification, object: nil) + requestDetailData() vp_setupUI() @@ -157,10 +163,25 @@ extension VPDetailPlayerViewController { ///打开充值页面 private func onRecharge() { + guard let videoInfo = self.viewModel.currentPlayer?.videoInfo else { return } + let view = VPPlayerRechargeView() + view.shortPlayId = videoInfo.short_play_id + view.videoId = videoInfo.short_play_video_id view.present(in: nil) + } + + ///成功开通会员 + @objc private func buyVipFinishNotification() { + guard VPLoginManager.manager.userInfo?.is_vip == true else { return } + self.detailModel?.episodeList?.forEach({ + $0.is_lock = false + }) + self.reloadData { [weak self] in + self?.play() + } } } diff --git a/Veloria/Class/Player/View/VPPlayerCoinBuyView.swift b/Veloria/Class/Player/View/VPPlayerCoinBuyView.swift index 4f0ca84..83275d4 100644 --- a/Veloria/Class/Player/View/VPPlayerCoinBuyView.swift +++ b/Veloria/Class/Player/View/VPPlayerCoinBuyView.swift @@ -16,6 +16,9 @@ class VPPlayerCoinBuyView: UIView { } } + var shortPlayId: String? + var videoId: String? + private lazy var selectedIndex = 0 private lazy var collectionViewLayout: UICollectionViewFlowLayout = { @@ -100,6 +103,12 @@ extension VPPlayerCoinBuyView: UICollectionViewDelegate, UICollectionViewDataSou func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { self.selectedIndex = indexPath.row collectionView.reloadData() + + VPIAPManager.manager.start(model: dataArr[indexPath.row], shortPlayId: self.shortPlayId, videoId: self.videoId) { finish in + if finish { + VPLoginManager.manager.updateUserInfo(completer: nil) + } + } } } diff --git a/Veloria/Class/Player/View/VPPlayerRechargeView.swift b/Veloria/Class/Player/View/VPPlayerRechargeView.swift index dbeebfb..77596d2 100644 --- a/Veloria/Class/Player/View/VPPlayerRechargeView.swift +++ b/Veloria/Class/Player/View/VPPlayerRechargeView.swift @@ -9,6 +9,19 @@ import UIKit class VPPlayerRechargeView: HWPanModalContentView { + var shortPlayId: String? { + didSet { + vipView.shortPlayId = shortPlayId + coinView.shortPlayId = shortPlayId + } + } + var videoId: String? { + didSet { + vipView.videoId = videoId + coinView.videoId = videoId + } + } + //MARK: UI属性 private lazy var bgView: UIImageView = { let imageView = UIImageView(image: UIImage(named: "bg_image_01")) @@ -48,8 +61,15 @@ class VPPlayerRechargeView: HWPanModalContentView { return view }() + deinit { + NotificationCenter.default.removeObserver(self) + } + override init(frame: CGRect) { super.init(frame: frame) + NotificationCenter.default.addObserver(self, selector: #selector(updateCoin), name: VPLoginManager.userInfoUpdateNotification, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(buyVipFinishNotification), name: VPIAPManager.buyVipFinishNotification, object: nil) + vp_setupUI() updateCoin() @@ -111,7 +131,7 @@ class VPPlayerRechargeView: HWPanModalContentView { extension VPPlayerRechargeView { - private func updateCoin() { + @objc private func updateCoin() { let coinCountStr = "\(VPLoginManager.manager.userInfo?.totalCoin ?? 0)" let text = String(format: "Coins: %@".localized, coinCountStr) let coinRange = text.ocString().range(of: coinCountStr) @@ -122,6 +142,13 @@ extension VPPlayerRechargeView { coinLabel.attributedText = string } + + @objc private func buyVipFinishNotification() { + if VPLoginManager.manager.userInfo?.is_vip == true { + self.dismiss(animated: true) { + } + } + } } extension VPPlayerRechargeView { diff --git a/Veloria/Class/Player/View/VPPlayerVipBuyView.swift b/Veloria/Class/Player/View/VPPlayerVipBuyView.swift index af87225..49b429b 100644 --- a/Veloria/Class/Player/View/VPPlayerVipBuyView.swift +++ b/Veloria/Class/Player/View/VPPlayerVipBuyView.swift @@ -15,6 +15,9 @@ class VPPlayerVipBuyView: UIView { } } + var shortPlayId: String? + var videoId: String? + private lazy var currentIndex: Int = 0 private lazy var collectionViewLayout: UICollectionViewFlowLayout = { @@ -140,5 +143,10 @@ extension VPPlayerVipBuyView: UICollectionViewDelegate, UICollectionViewDataSour self.collectionView.reloadData() } + VPIAPManager.manager.start(model: dataArr[indexPath.row], shortPlayId: self.shortPlayId, videoId: self.videoId) { finish in + if finish { + VPLoginManager.manager.updateUserInfo(completer: nil) + } + } } } diff --git a/Veloria/Class/Player/View/VPVideoLockView.swift b/Veloria/Class/Player/View/VPVideoLockView.swift index 2048b05..1342732 100644 --- a/Veloria/Class/Player/View/VPVideoLockView.swift +++ b/Veloria/Class/Player/View/VPVideoLockView.swift @@ -42,21 +42,24 @@ class VPVideoLockView: UIView { }() private lazy var coinCountLabel: UILabel = { - let userInfo = VPLoginManager.manager.userInfo - let label = UILabel() label.font = .fontRegular(ofSize: 12) label.textColor = .colorB5B5B5() - label.text = String(format: "Balance: %@ Coins | %@ Bonus".localized, "\(userInfo?.coin_left_total ?? 0)", "\(userInfo?.send_coin_left_total ?? 0)") return label }() + deinit { + NotificationCenter.default.removeObserver(self) + } override init(frame: CGRect) { super.init(frame: frame) + NotificationCenter.default.addObserver(self, selector: #selector(userInfoUpdateNotification), name: VPLoginManager.userInfoUpdateNotification, object: nil) backgroundColor = .color000000(alpha: 0.8) + userInfoUpdateNotification() + vp_setupUI() } @@ -68,6 +71,12 @@ class VPVideoLockView: UIView { self.clickUnlockButton?() } + @objc private func userInfoUpdateNotification() { + let userInfo = VPLoginManager.manager.userInfo + + coinCountLabel.text = String(format: "Balance: %@ Coins | %@ Bonus".localized, "\(userInfo?.coin_left_total ?? 0)", "\(userInfo?.send_coin_left_total ?? 0)") + } + } extension VPVideoLockView { diff --git a/Veloria/Class/Wallet/Controller/VPCoinsViewController.swift b/Veloria/Class/Wallet/Controller/VPCoinsViewController.swift index 4eb5084..7f14fe9 100644 --- a/Veloria/Class/Wallet/Controller/VPCoinsViewController.swift +++ b/Veloria/Class/Wallet/Controller/VPCoinsViewController.swift @@ -73,11 +73,16 @@ class VPCoinsViewController: VPViewController { label.text = "kStoreTips".localized return label }() + + deinit { + NotificationCenter.default.removeObserver(self) + } override func viewDidLoad() { super.viewDidLoad() self.bgImageView.isHidden = true self.view.backgroundColor = .clear + NotificationCenter.default.addObserver(self, selector: #selector(userInfoUpdateNotification), name: VPLoginManager.userInfoUpdateNotification, object: nil) updateCoin() vp_setupUI() @@ -112,6 +117,10 @@ class VPCoinsViewController: VPViewController { self.collectionView.reloadData() CATransaction.commit() } + + @objc private func userInfoUpdateNotification() { + updateCoin() + } } extension VPCoinsViewController { @@ -176,5 +185,14 @@ extension VPCoinsViewController: UICollectionViewDelegate, UICollectionViewDataS func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { self.selectedIndex = indexPath.row collectionView.reloadData() + + VPIAPManager.manager.start(model: dataArr[indexPath.row]) { finish in + if finish { + VPLoginManager.manager.updateUserInfo(completer: nil) + } + } + } } + + diff --git a/Veloria/Class/Wallet/Controller/VPVipPageViewController.swift b/Veloria/Class/Wallet/Controller/VPVipPageViewController.swift index bfa6ff2..598b8af 100644 --- a/Veloria/Class/Wallet/Controller/VPVipPageViewController.swift +++ b/Veloria/Class/Wallet/Controller/VPVipPageViewController.swift @@ -95,7 +95,9 @@ class VPVipPageViewController: VPViewController { override func viewDidLoad() { super.viewDidLoad() - + let rightBar = UIBarButtonItem(title: "Restore".localized, style: .plain, target: self, action: #selector(handleRestore)) + self.navigationItem.rightBarButtonItem = rightBar + vp_setupUI() requestData() @@ -115,6 +117,14 @@ class VPVipPageViewController: VPViewController { button.setBackgroundImage(UIImage(named: "vip_menu_right_selected"), for: .selected) } } + + @objc private func handleRestore() { + VPIAPManager.manager.restore(isLoding: true) { isFinish in + if isFinish { + VPLoginManager.manager.updateUserInfo(completer: nil) + } + } + } } extension VPVipPageViewController { diff --git a/Veloria/Class/Wallet/Controller/VPVipViewController.swift b/Veloria/Class/Wallet/Controller/VPVipViewController.swift index 2636256..1bd7f32 100644 --- a/Veloria/Class/Wallet/Controller/VPVipViewController.swift +++ b/Veloria/Class/Wallet/Controller/VPVipViewController.swift @@ -219,5 +219,11 @@ extension VPVipViewController: UICollectionViewDelegate, UICollectionViewDataSou self.collectionView.reloadData() } + VPIAPManager.manager.start(model: dataArr[indexPath.row]) { finish in + if finish { + VPLoginManager.manager.updateUserInfo(completer: nil) + } + } + } } diff --git a/Veloria/Libs/VPIAPManager/VPIAPManager.swift b/Veloria/Libs/VPIAPManager/VPIAPManager.swift new file mode 100644 index 0000000..0af0353 --- /dev/null +++ b/Veloria/Libs/VPIAPManager/VPIAPManager.swift @@ -0,0 +1,193 @@ +// +// VPIAPManager.swift +// Veloria +// +// Created by 湖南秦九 on 2025/6/5. +// + +import UIKit + +class VPIAPManager { + + typealias CompletionHandler = ((_ finish: Bool) -> Void) + ///内购模版前缀 + static let IAPPrefix = "veloria." + + + static let manager = VPIAPManager() + + ///成功回调 + private var completionHandler: CompletionHandler? + + private lazy var iapManager: JXIAPManager = { + let manager = JXIAPManager() + manager.delegate = self + return manager + }() + + private var orderCode: String? + private var payId: String? + + ///恢复购买使用 + ///等待恢复的数据 + private var waitRestoreModel: VPWaitRestoreModel? = UserDefaults.vp_object(forKey: kVPWaitRestoreIAPDefaultsKey, as: VPWaitRestoreModel.self) + + + + ///开始内购 + func start(model: VPPayTemplateItem, shortPlayId: String? = nil, videoId: String? = nil, handler: CompletionHandler? = nil) { + + if let _ = self.waitRestoreModel { + VPToast.show(text: "kIapErrorToast01".localized) + handler?(false) + return + } + + guard let payId = model.id else { + handler?(false) + return + } + self.completionHandler = handler + self.waitRestoreModel = VPWaitRestoreModel() + self.waitRestoreModel?.buyType = model.buy_type + + let productId = VPIAPManager.IAPPrefix + (model.ios_template_id ?? "") + + VPHUD.show() + + VPWalletAPI.requestCreateOrder(payId: payId, shortPlayId: shortPlayId ?? "0", videoId: videoId ?? "0") { orderModel in + guard let orderModel = orderModel else { + VPHUD.dismiss() + self.waitRestoreModel = nil + self.completionHandler?(false) + return + } + self.orderCode = orderModel.order_code + self.payId = payId + self.waitRestoreModel?.payId = payId + self.waitRestoreModel?.orderCode = orderModel.order_code + + self.iapManager.start(productId: productId, orderId: self.orderCode ?? "") + } + + } + + func restore(isLoding: Bool = true, completer: ((_ isFinish: Bool) -> Void)?) { + guard let waitRestoreModel = self.waitRestoreModel, + let orderCode = waitRestoreModel.orderCode, + let payId = waitRestoreModel.payId, + let productId = waitRestoreModel.productId, + let receipt = waitRestoreModel.receipt + else { + if isLoding { + VPToast.show(text: "kIapErrorToast03".localized) + } + return + } + + if isLoding { + VPHUD.show() + } + VPWalletAPI.requestVerifyOrder(orderCode: orderCode, payId: payId, productId: productId, purchaseToken: receipt) { model in + if isLoding { + VPHUD.dismiss() + } + + guard let model = model else { + completer?(false) + return + } + let buyType = self.waitRestoreModel?.buyType + self.waitRestoreModel = nil + UserDefaults.vp_setObject(self.waitRestoreModel, forKey: kVPWaitRestoreIAPDefaultsKey) + + if model.status == "success" { + if buyType == .subVip { + VPLoginManager.manager.userInfo?.is_vip = true + } + + if isLoding { + VPToast.show(text: "Success".localized) + } + completer?(true) + if buyType == .subVip { + NotificationCenter.default.post(name: VPIAPManager.buyVipFinishNotification, object: nil) + } + } else { + completer?(false) + } + } + + } + +} + +//MARK: -------------- JXIAPManagerDelegate -------------- +extension VPIAPManager: JXIAPManagerDelegate { + + func jx_iapPaySuccess(productId: String, receipt: String, transactionIdentifier: String?) { + guard let orderCode = self.orderCode, let payId = self.payId else { + self.orderCode = nil + self.payId = nil + self.waitRestoreModel = nil + self.completionHandler?(false) + return + } + + self.waitRestoreModel?.productId = productId + self.waitRestoreModel?.receipt = receipt + + UserDefaults.vp_setObject(self.waitRestoreModel, forKey: kVPWaitRestoreIAPDefaultsKey) + + VPWalletAPI.requestVerifyOrder(orderCode: orderCode, payId: payId, productId: productId, purchaseToken: receipt) { model in + VPHUD.dismiss() + + self.orderCode = nil + self.payId = nil + + guard let model = model else { + self.completionHandler?(false) + return + } + + let buyType = self.waitRestoreModel?.buyType + self.waitRestoreModel = nil + UserDefaults.vp_setObject(self.waitRestoreModel, forKey: kVPWaitRestoreIAPDefaultsKey) + + if model.status == "success" { + if buyType == .subVip { + VPLoginManager.manager.userInfo?.is_vip = true + } + + VPToast.show(text: "Success".localized) + self.completionHandler?(true) + if buyType == .subVip { + NotificationCenter.default.post(name: VPIAPManager.buyVipFinishNotification, object: nil) + } + } else { + self.completionHandler?(false) + } + } + + } + + func jx_iapPayFailed(productId: String, code: JXIAPManagerCode) { + self.orderCode = nil + self.payId = nil + self.waitRestoreModel = nil + + VPHUD.dismiss() + + if code == .noProduct { + VPToast.show(text: "kIapErrorToast02".localized) + } + self.completionHandler?(false) + } + +} + +extension VPIAPManager { + ///成功购买会员 + @objc static let buyVipFinishNotification = NSNotification.Name(rawValue: "VPIAPManager.buyVipFinishNotification") + +} diff --git a/Veloria/Libs/VPIAPManager/VPIAPOrderModel.swift b/Veloria/Libs/VPIAPManager/VPIAPOrderModel.swift new file mode 100644 index 0000000..82d4e35 --- /dev/null +++ b/Veloria/Libs/VPIAPManager/VPIAPOrderModel.swift @@ -0,0 +1,19 @@ +// +// VPIAPOrderModel.swift +// Veloria +// +// Created by 湖南秦九 on 2025/6/5. +// + +import UIKit +import SmartCodable + +class VPIAPOrderModel: VPModel, SmartCodable { + + var code: Int? + var message: String? + var money: String? + var order_code: String? + var is_backhaul: String? + +} diff --git a/Veloria/Libs/VPIAPManager/VPIAPVerifyModel.swift b/Veloria/Libs/VPIAPManager/VPIAPVerifyModel.swift new file mode 100644 index 0000000..18643bf --- /dev/null +++ b/Veloria/Libs/VPIAPManager/VPIAPVerifyModel.swift @@ -0,0 +1,17 @@ +// +// VPIAPVerifyModel.swift +// Veloria +// +// Created by 湖南秦九 on 2025/6/5. +// + +import UIKit +import SmartCodable + +class VPIAPVerifyModel: VPModel, SmartCodable { + + var code: String? + var status: String? + var money: String? + var is_backhaul: String? +} diff --git a/Veloria/Libs/VPIAPManager/VPWaitRestoreModel.swift b/Veloria/Libs/VPIAPManager/VPWaitRestoreModel.swift new file mode 100644 index 0000000..aa1c741 --- /dev/null +++ b/Veloria/Libs/VPIAPManager/VPWaitRestoreModel.swift @@ -0,0 +1,45 @@ +// +// VPWaitRestoreModel.swift +// Veloria +// +// Created by 湖南秦九 on 2025/6/5. +// + +import UIKit + +class VPWaitRestoreModel: VPModel, NSSecureCoding { + + var orderCode: String? + var payId: String? + var productId: String? + var receipt: String? + var buyType: VPWalletAPI.BuyType? + + + required init() { } + + static var supportsSecureCoding: Bool { + get { + return true + } + } + + func encode(with coder: NSCoder) { + coder.encode(orderCode, forKey: "orderCode") + coder.encode(payId, forKey: "payId") + coder.encode(productId, forKey: "productId") + coder.encode(receipt, forKey: "receipt") + coder.encode(buyType?.rawValue, forKey: "buyType") + } + + required init?(coder: NSCoder) { + super.init() + orderCode = coder.decodeObject(of: NSString.self, forKey: "orderCode") as? String + payId = coder.decodeObject(of: NSString.self, forKey: "payId") as? String + productId = coder.decodeObject(of: NSString.self, forKey: "productId") as? String + receipt = coder.decodeObject(of: NSString.self, forKey: "receipt") as? String + if let type = coder.decodeObject(of: NSString.self, forKey: "buyType") as? String { + buyType = VPWalletAPI.BuyType(rawValue: type) + } + } +} diff --git a/Veloria/Source/en.lproj/Localizable.strings b/Veloria/Source/en.lproj/Localizable.strings index e8be9ea..d57cad4 100644 --- a/Veloria/Source/en.lproj/Localizable.strings +++ b/Veloria/Source/en.lproj/Localizable.strings @@ -84,6 +84,8 @@ "Check in" = "Check in"; "Expires in %@ days" = "Expires in 30 days"; "Expired" = "Expired"; +"Success" = "Success"; +"Restore" = "Restore"; "kHomeTitleText" = "10,000+ addictive shorts await!"; "kSearchPlaceholderText1" = "Search dramas"; @@ -103,6 +105,13 @@ "kLockPreviousEpisodeText" = "The prequel to this series is not unlocked. Please unlock the prequel before unlocking this series"; //解锁失败 "kLockFailText" = "Purchase failed, please try again later!"; +//已是会员 +"kVipToast01" = "You are already a member!"; +//还有未完成购买 +"kIapErrorToast01" = "You have unfinished in-app purchases, please restore them first."; +"kIapErrorToast02" = "Invalid in-app purchase"; +///没有可恢复购买 +"kIapErrorToast03" = "There are no in-app purchases to restore."; diff --git a/Veloria/Thirdparty/JXIAPManager/JXIAPManager.swift b/Veloria/Thirdparty/JXIAPManager/JXIAPManager.swift new file mode 100644 index 0000000..5a41138 --- /dev/null +++ b/Veloria/Thirdparty/JXIAPManager/JXIAPManager.swift @@ -0,0 +1,207 @@ +// +// JXIAPManager.swift +// BoJia +// +// Created by 火山传媒 on 2024/6/3. +// + +import UIKit +import StoreKit + +@objc protocol JXIAPManagerDelegate { + /// 获取到可购买商品列表 + @objc optional func jx_iapPayGotProducts(productIds: [String]) + /// 购买成功 + @objc optional func jx_iapPaySuccess(productId: String, receipt: String, transactionIdentifier: String?) + /// 购买失败 + @objc optional func jx_iapPayFailed(productId: String, code: JXIAPManagerCode) + /// 恢复商品(仅限永久有效商品) + @objc optional func iapPayRestore(productIds: [String], transactionIds: [String]) +// /// 加载 +// @objc optional func iapPayShowHud() +// /// 系统错误 +// @objc optional func iapSysWrong() +// /// 验证成功 +// @objc optional func verifySuccess() +// /// 验证失败 +// @objc optional func verifyFailed() +} + +@objc enum JXIAPManagerCode: Int { + ///未知错误 + case unknown + ///取消交易 + case cancelled + ///没有商品 + case noProduct + +} + +class JXIAPManager: NSObject { + + static let manager: JXIAPManager = JXIAPManager() + + weak var delegate: JXIAPManagerDelegate? + + private var payment: SKPayment? + + private var product: SKProduct? + private var productId: String? + private var orderId: String? + private var applicationUsername: String? { + get { + let id = "00000000-0000-0000-0000-000000000000" + guard let orderId = orderId else { return nil } + var string = "" + for i in 0.. Bool { +// return true +// } + + /// 恢复购买回调 + func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) { + + } + +} + +extension JXIAPManager { + private func completeTransaction(transaction: SKPaymentTransaction) { + //自动续费 +// if let _ = transaction.original, transaction.payment.applicationUsername == nil { +// return +// } + //重新开通自动续费 +// if let _ = transaction.original, transaction.payment.applicationUsername != nil { +// self.delegate?.jx_iapPayFailed?(productId: productId, code: .unknown) +// return +// } + + guard let productId = self.productId, productId == transaction.payment.productIdentifier else { return } + self.productId = nil + + guard let receiptURL = Bundle.main.appStoreReceiptURL else { return } + let receiptData = NSData(contentsOf: receiptURL) + guard let encodeStr = receiptData?.base64EncodedString(options: .endLineWithLineFeed) else { return } + guard let transactionIdentifier = transaction.transactionIdentifier else { return } + + self.delegate?.jx_iapPaySuccess?(productId: productId, receipt: encodeStr, transactionIdentifier: transactionIdentifier) + + } + + private func failedTransaction(transaction: SKPaymentTransaction) { + let error = transaction.error as? SKError + guard let productId = self.productId else { return } + self.productId = nil + + switch error?.code { + case SKError.paymentCancelled: + self.delegate?.jx_iapPayFailed?(productId: productId, code: .cancelled) + default: + self.delegate?.jx_iapPayFailed?(productId: productId, code: .unknown) + } + + } +}