充值会员相关UI开发
This commit is contained in:
parent
c3944c5342
commit
05e80f50e2
@ -17,6 +17,8 @@ extension AppDelegate {
|
||||
SPToast.config()
|
||||
|
||||
SPHUD.config()
|
||||
|
||||
UIBarButtonItem.appearance().sp_setTitleTextAttributes()
|
||||
}
|
||||
|
||||
|
||||
@ -70,8 +72,6 @@ extension AppDelegate {
|
||||
if #available(iOS 15.0, *) {
|
||||
tabBar.scrollEdgeAppearance = appearance
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
36
MoviaBox/Base/Extension/UIBarButtonItem+SPAdd.swift
Normal file
36
MoviaBox/Base/Extension/UIBarButtonItem+SPAdd.swift
Normal file
@ -0,0 +1,36 @@
|
||||
//
|
||||
// UIBarButtonItem+SPAdd.swift
|
||||
// MoviaBox
|
||||
//
|
||||
// Created by 佳尔 on 2025/4/29.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
extension UIBarButtonItem {
|
||||
|
||||
static let sp_normalTitleFont = UIFont.fontRegular(ofSize: 14)
|
||||
static var sp_normalTitleColor: UIColor {
|
||||
get {
|
||||
return UIColor.colorFFFFFF(alpha: 0.5)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
默认标题样式
|
||||
*/
|
||||
static var sp_normalTitleTextAttributes: [NSAttributedString.Key : Any] {
|
||||
get {
|
||||
return [
|
||||
NSAttributedString.Key.font : sp_normalTitleFont,
|
||||
NSAttributedString.Key.foregroundColor : sp_normalTitleColor
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
func sp_setTitleTextAttributes(normalAttributes: [NSAttributedString.Key : Any] = UIBarButtonItem.sp_normalTitleTextAttributes) {
|
||||
self.setTitleTextAttributes(normalAttributes, for: .normal)
|
||||
self.setTitleTextAttributes(normalAttributes, for: .highlighted)
|
||||
}
|
||||
|
||||
}
|
@ -94,7 +94,7 @@ class SPMineWalletView: UIView {
|
||||
|
||||
extension SPMineWalletView {
|
||||
@objc private func handleStoreButton() {
|
||||
let vc = SPWalletViewController()
|
||||
let vc = SPStoreViewController()
|
||||
self.viewController?.navigationController?.pushViewController(vc, animated: true)
|
||||
}
|
||||
|
||||
|
138
MoviaBox/Class/Wallet/Controller/SPStoreViewController.swift
Normal file
138
MoviaBox/Class/Wallet/Controller/SPStoreViewController.swift
Normal file
@ -0,0 +1,138 @@
|
||||
//
|
||||
// SPStoreViewController.swift
|
||||
// MoviaBox
|
||||
//
|
||||
// Created by 佳尔 on 2025/4/29.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class SPStoreViewController: SPViewController {
|
||||
|
||||
//MARK: UI属性
|
||||
private lazy var scrollView: SPScrollView = {
|
||||
let scrollView = SPScrollView()
|
||||
return scrollView
|
||||
}()
|
||||
|
||||
private lazy var stackView: UIStackView = {
|
||||
let view = UIStackView()
|
||||
view.axis = .vertical
|
||||
view.spacing = 16
|
||||
return view
|
||||
}()
|
||||
|
||||
private lazy var rechargeView: SPCoinRechargeView = {
|
||||
let view = SPCoinRechargeView()
|
||||
return view
|
||||
}()
|
||||
|
||||
private lazy var memberView: SPMemberRechargeView = {
|
||||
let view = SPMemberRechargeView()
|
||||
return view
|
||||
}()
|
||||
|
||||
private lazy var tipTitleLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.font = .fontMedium(ofSize: 12)
|
||||
label.textColor = .colorFFFFFF(alpha: 0.7)
|
||||
return label
|
||||
}()
|
||||
|
||||
private lazy var tipTextLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.numberOfLines = 0
|
||||
label.textColor = .colorFFFFFF(alpha: 0.5)
|
||||
label.font = .fontRegular(ofSize: 12)
|
||||
return label
|
||||
}()
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
self.title = "Store".localized
|
||||
self.edgesForExtendedLayout = .top
|
||||
setBackgroundView(isShowGradient: false, bgImage: UIImage(named: "buy_bg_image_02"))
|
||||
|
||||
let rightBarButton = UIBarButtonItem(title: "Restore".localized, style: .plain, target: self, action: #selector(handelRightBarButton))
|
||||
self.navigationItem.rightBarButtonItem = rightBarButton
|
||||
|
||||
tipTitleLabel.text = "kStoreTipTitle".localized
|
||||
tipTextLabel.text = "kStoreTipText".localized
|
||||
|
||||
_setupUI()
|
||||
|
||||
requestPayTemplate()
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
self.navigationController?.setNavigationBarHidden(false, animated: true)
|
||||
setNavigationNormalStyle(backgroundColor: .clear, isTranslucent: true)
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
extension SPStoreViewController {
|
||||
|
||||
@objc private func handelRightBarButton() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension SPStoreViewController {
|
||||
|
||||
private func _setupUI() {
|
||||
view.addSubview(scrollView)
|
||||
scrollView.addSubview(stackView)
|
||||
scrollView.addSubview(tipTitleLabel)
|
||||
scrollView.addSubview(tipTextLabel)
|
||||
|
||||
scrollView.snp.makeConstraints { make in
|
||||
make.left.right.bottom.equalToSuperview()
|
||||
make.top.equalToSuperview().offset(kSPNavBarHeight)
|
||||
}
|
||||
|
||||
stackView.snp.makeConstraints { make in
|
||||
make.left.right.top.equalToSuperview()
|
||||
// make.bottom.equalTo(-(kSPTabbarSafeBottomMargin + 10))
|
||||
make.width.equalTo(kSPScreenWidth)
|
||||
}
|
||||
|
||||
tipTitleLabel.snp.makeConstraints { make in
|
||||
make.left.equalToSuperview().offset(24)
|
||||
make.top.equalTo(stackView.snp.bottom).offset(34)
|
||||
}
|
||||
|
||||
tipTextLabel.snp.makeConstraints { make in
|
||||
make.left.equalTo(tipTitleLabel)
|
||||
make.right.lessThanOrEqualTo(stackView).offset(-24)
|
||||
make.top.equalTo(tipTitleLabel.snp.bottom).offset(4)
|
||||
make.bottom.equalTo(-(kSPTabbarSafeBottomMargin + 10))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension SPStoreViewController {
|
||||
|
||||
///请求支付模版
|
||||
private func requestPayTemplate() {
|
||||
SPWalletAPI.requestPayTemplate { [weak self] templateModel in
|
||||
guard let self = self else { return }
|
||||
self.rechargeView.dataArr = templateModel?.list_coins
|
||||
self.memberView.dataArr = templateModel?.list_sub_vip
|
||||
|
||||
self.stackView.removeAllArrangedSubview()
|
||||
|
||||
self.stackView.addArrangedSubview(self.memberView)
|
||||
self.stackView.addArrangedSubview(self.rechargeView)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
22
MoviaBox/Source/Assets.xcassets/image/buy_bg_image_02.imageset/Contents.json
vendored
Normal file
22
MoviaBox/Source/Assets.xcassets/image/buy_bg_image_02.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "背景.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "背景@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
MoviaBox/Source/Assets.xcassets/image/buy_bg_image_02.imageset/背景.png
vendored
Normal file
BIN
MoviaBox/Source/Assets.xcassets/image/buy_bg_image_02.imageset/背景.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.2 MiB |
BIN
MoviaBox/Source/Assets.xcassets/image/buy_bg_image_02.imageset/背景@2x.png
vendored
Normal file
BIN
MoviaBox/Source/Assets.xcassets/image/buy_bg_image_02.imageset/背景@2x.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.0 MiB |
@ -73,4 +73,6 @@
|
||||
|
||||
"kLoginAgreementText" = "By continuing, you agree to the User Agreement and Privacy Policy";
|
||||
"kBuyMemberTipText" = "Auto renew · Cancel anytime";
|
||||
"kStoreTipTitle" = "Related terms and conditions:";
|
||||
"kStoreTipText" = "1. Coins can only be used within this application.
2. Payment: The purchase will be charged to your iTunes account.
3. Renewal: Your Apple iTunes account will be charged within 24 hours before the expiration and the subscription period will be extended for another subscription cycle upon successful deduction.
4. Cancellation: To cancel the subscription renewal, please turn off the automatic renewal function in the iTunes/Apple ID settings at least 24 hours before the current subscription period expires. If canceled within the last 24 hours before expiration, a subscription fee will still be charged.
5. Payment successful but recharge not taking effect for an extended period? Click here to refresh or send an email to: cs.jiaer.developer@icloud.com.
6. Manage your subscriptions: You can view, change, or cancel your subscriptions.
Terms of Service | Privacy Policy
Renewal Agreement";
|
||||
|
||||
|
198
MoviaBox/Thirdparty/JXIAPManager/JXIAPManager.swift
vendored
Normal file
198
MoviaBox/Thirdparty/JXIAPManager/JXIAPManager.swift
vendored
Normal file
@ -0,0 +1,198 @@
|
||||
//
|
||||
// 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
|
||||
tmLog(message: payment.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 {
|
||||
self.delegate?.jx_iapPayFailed?(productId: self.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 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
|
||||
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