内购功能开发,配置内购商品
This commit is contained in:
parent
6d690cc471
commit
337c25baaf
@ -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 = "<group>"; };
|
||||
BFF5B25B2DF13F850044227A /* Date+VPAdd.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+VPAdd.swift"; sourceTree = "<group>"; };
|
||||
BFF5B25D2DF1423F0044227A /* VPWalletBaseCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPWalletBaseCell.swift; sourceTree = "<group>"; };
|
||||
BFF5B25F2DF16B430044227A /* JXIAPManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JXIAPManager.swift; sourceTree = "<group>"; };
|
||||
BFF5B2632DF16C380044227A /* VPIAPManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPIAPManager.swift; sourceTree = "<group>"; };
|
||||
BFF5B2652DF16CF60044227A /* VPWaitRestoreModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPWaitRestoreModel.swift; sourceTree = "<group>"; };
|
||||
BFF5B2672DF16EA30044227A /* VPIAPOrderModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPIAPOrderModel.swift; sourceTree = "<group>"; };
|
||||
BFF5B2692DF170DD0044227A /* VPIAPVerifyModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPIAPVerifyModel.swift; sourceTree = "<group>"; };
|
||||
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 = "<group>"; };
|
||||
/* 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 = "<group>";
|
||||
};
|
||||
BFF5B2602DF16B430044227A /* JXIAPManager */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
BFF5B25F2DF16B430044227A /* JXIAPManager.swift */,
|
||||
);
|
||||
path = JXIAPManager;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
BFF5B2622DF16BE10044227A /* VPIAPManager */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
BFF5B2632DF16C380044227A /* VPIAPManager.swift */,
|
||||
BFF5B2652DF16CF60044227A /* VPWaitRestoreModel.swift */,
|
||||
BFF5B2672DF16EA30044227A /* VPIAPOrderModel.swift */,
|
||||
BFF5B2692DF170DD0044227A /* VPIAPVerifyModel.swift */,
|
||||
);
|
||||
path = VPIAPManager;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* 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 */,
|
||||
|
@ -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)
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -21,6 +21,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
|
||||
VPLoginManager.manager.updateUserInfo(completer: nil)
|
||||
|
||||
let _ = JXIAPManager.manager
|
||||
|
||||
self.registThirdparty(application, didFinishLaunchingWithOptions: launchOptions)
|
||||
|
||||
return true
|
||||
|
@ -15,3 +15,6 @@ let kVPLoginTokenDefaultsKey = "kVPLoginTokenDefaultsKey"
|
||||
|
||||
///用户信息
|
||||
let kVPLoginUserInfoDefaultsKey = "kSPLoginUserInfoDefaultsKey"
|
||||
|
||||
///待恢复数据
|
||||
let kVPWaitRestoreIAPDefaultsKey = "kVPWaitRestoreIAPDefaultsKey"
|
||||
|
@ -20,6 +20,21 @@ class VPWalletAPI {
|
||||
param.method = .get
|
||||
|
||||
VPNetwork.request(parameters: param) { (response: VPNetworkResponse<VPPayTemplateModel>) 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<VPIAPOrderModel>) 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<VPIAPVerifyModel>) in
|
||||
completer?(response.data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
193
Veloria/Libs/VPIAPManager/VPIAPManager.swift
Normal file
193
Veloria/Libs/VPIAPManager/VPIAPManager.swift
Normal file
@ -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")
|
||||
|
||||
}
|
19
Veloria/Libs/VPIAPManager/VPIAPOrderModel.swift
Normal file
19
Veloria/Libs/VPIAPManager/VPIAPOrderModel.swift
Normal file
@ -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?
|
||||
|
||||
}
|
17
Veloria/Libs/VPIAPManager/VPIAPVerifyModel.swift
Normal file
17
Veloria/Libs/VPIAPManager/VPIAPVerifyModel.swift
Normal file
@ -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?
|
||||
}
|
45
Veloria/Libs/VPIAPManager/VPWaitRestoreModel.swift
Normal file
45
Veloria/Libs/VPIAPManager/VPWaitRestoreModel.swift
Normal file
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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.";
|
||||
|
||||
|
||||
|
||||
|
207
Veloria/Thirdparty/JXIAPManager/JXIAPManager.swift
vendored
Normal file
207
Veloria/Thirdparty/JXIAPManager/JXIAPManager.swift
vendored
Normal file
@ -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..<orderId.length() {
|
||||
if i == 12 || i == 16 || i == 20 || i == 24 {
|
||||
string.insert("-", at: string.startIndex)
|
||||
}
|
||||
let s = orderId[orderId.index(orderId.endIndex, offsetBy: -(i + 1))]
|
||||
string.insert(s, at: string.startIndex)
|
||||
}
|
||||
|
||||
let length = id.length()
|
||||
let stringLength = string.length()
|
||||
|
||||
if stringLength <= length {
|
||||
let range = NSRange(location: length - string.length(), length: string.length())
|
||||
return id.ocString().replacingCharacters(in: range, with: string)
|
||||
} else {
|
||||
return string
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
SKPaymentQueue.default().add(self)
|
||||
}
|
||||
|
||||
func start(productId: String, orderId: String) {
|
||||
self.product = nil
|
||||
self.productId = productId
|
||||
self.orderId = orderId
|
||||
|
||||
let set = Set([productId])
|
||||
let productsRequest = SKProductsRequest(productIdentifiers: set)
|
||||
productsRequest.delegate = self
|
||||
productsRequest.start()
|
||||
}
|
||||
|
||||
/// 购买商品
|
||||
private func buyProduct() {
|
||||
guard let product = self.product else { return }
|
||||
|
||||
// 要购买商品,开个小票
|
||||
let payment = SKMutablePayment(product: product)
|
||||
payment.applicationUsername = applicationUsername
|
||||
|
||||
self.payment = payment
|
||||
// 去收银台排队,准备购买
|
||||
SKPaymentQueue.default().add(payment)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//MARK: -------------- SKProductsRequestDelegate --------------
|
||||
extension JXIAPManager: SKProductsRequestDelegate {
|
||||
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
|
||||
guard let product = response.products.first else {
|
||||
DispatchQueue.main.async {
|
||||
if let productId = self.productId {
|
||||
self.productId = nil
|
||||
self.delegate?.jx_iapPayFailed?(productId: productId, code: .noProduct)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
self.product = product
|
||||
|
||||
self.buyProduct()
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: -------------- SKPaymentTransactionObserver --------------
|
||||
extension JXIAPManager: SKPaymentTransactionObserver {
|
||||
///购买回调
|
||||
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
|
||||
|
||||
for transaction in transactions {
|
||||
switch transaction.transactionState {
|
||||
case .purchased:
|
||||
DispatchQueue.main.async {
|
||||
self.completeTransaction(transaction: transaction)
|
||||
}
|
||||
SKPaymentQueue.default().finishTransaction(transaction)
|
||||
|
||||
case .failed:
|
||||
DispatchQueue.main.async {
|
||||
self.failedTransaction(transaction: transaction)
|
||||
}
|
||||
SKPaymentQueue.default().finishTransaction(transaction)
|
||||
// case .restored:
|
||||
// self.restoreTransaction(transaction: transaction)
|
||||
|
||||
case .purchasing:
|
||||
break
|
||||
default:
|
||||
SKPaymentQueue.default().finishTransaction(transaction)
|
||||
break
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// func paymentQueue(_ queue: SKPaymentQueue, shouldAddStorePayment payment: SKPayment, for product: SKProduct) -> 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)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user