DolphinEnglishLearnStudent.xcodeproj/project.pbxproj | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
DolphinEnglishLearnStudent/Config/Config.swift | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
DolphinEnglishLearnStudent/Models/CommonModel.swift | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
DolphinEnglishLearnStudent/Moudle/Me/MeVC.swift | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
DolphinEnglishLearnStudent/Moudle/Me/VC/VIPCenterVC.swift | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
DolphinEnglishLearnStudent/Services/InPurchaseManager.swift | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
DolphinEnglishLearnStudent/Services/NetworkRequest.swift | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
DolphinEnglishLearnStudent/Services/Services.swift | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
DolphinEnglishLearnStudent/ViewModel/UserViewModel.swift | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
Podfile | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
DolphinEnglishLearnStudent.xcodeproj/project.pbxproj
@@ -78,6 +78,7 @@ 13649E982C002534001B04E2 /* HomeListenFight_lesson_1_VC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13649E972C002534001B04E2 /* HomeListenFight_lesson_1_VC.swift */; }; 13649E9C2C00304C001B04E2 /* ListenFight_lesson_1_CCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13649E9A2C00304C001B04E2 /* ListenFight_lesson_1_CCell.swift */; }; 13649E9D2C00304C001B04E2 /* ListenFight_lesson_1_CCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 13649E9B2C00304C001B04E2 /* ListenFight_lesson_1_CCell.xib */; }; 13736BF62C522F7B0068873E /* InPurchaseManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13736BF52C522F7B0068873E /* InPurchaseManager.swift */; }; 137CB4292BFF505800D32862 /* HomeListenFightVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 137CB4282BFF505800D32862 /* HomeListenFightVC.swift */; }; 13812B9C2C0F02B700905CCE /* VoicePlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13812B9B2C0F02B600905CCE /* VoicePlayer.swift */; }; 138964002BFDF98200AEDCD9 /* StudyVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 138963FE2BFDF98200AEDCD9 /* StudyVC.swift */; }; @@ -197,6 +198,7 @@ 13649E972C002534001B04E2 /* HomeListenFight_lesson_1_VC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = HomeListenFight_lesson_1_VC.swift; path = DolphinEnglishLearnStudent/Moudle/Home/HomeListenFight_lesson_1_VC.swift; sourceTree = SOURCE_ROOT; }; 13649E9A2C00304C001B04E2 /* ListenFight_lesson_1_CCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListenFight_lesson_1_CCell.swift; sourceTree = "<group>"; }; 13649E9B2C00304C001B04E2 /* ListenFight_lesson_1_CCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ListenFight_lesson_1_CCell.xib; sourceTree = "<group>"; }; 13736BF52C522F7B0068873E /* InPurchaseManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InPurchaseManager.swift; sourceTree = "<group>"; }; 137CB4282BFF505800D32862 /* HomeListenFightVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeListenFightVC.swift; sourceTree = "<group>"; }; 13812B9B2C0F02B600905CCE /* VoicePlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoicePlayer.swift; sourceTree = "<group>"; }; 138963FE2BFDF98200AEDCD9 /* StudyVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StudyVC.swift; sourceTree = "<group>"; }; @@ -462,6 +464,7 @@ 1319B02E2C08592D0052F889 /* Services */ = { isa = PBXGroup; children = ( 13736BF52C522F7B0068873E /* InPurchaseManager.swift */, 1319B0302C0859370052F889 /* NetworkRequest.swift */, 1319B02F2C0859370052F889 /* Services.swift */, ); @@ -782,6 +785,7 @@ 13A830F12C04196400BB2F23 /* HomeListenFight_lesson_3_VC.swift in Sources */, 13CDF44D2C056A6900E8D4FD /* ListenFight_lesson_4_CCell.swift in Sources */, 131C030B2C0D564000EA4C25 /* MarketTagCCell.swift in Sources */, 13736BF62C522F7B0068873E /* InPurchaseManager.swift in Sources */, 134C54B02C3785910009D09C /* ParentVerifiyView.swift in Sources */, 13CD3AC72C0874B3007E1065 /* CommonModel.swift in Sources */, 131C030D2C0D6A4800EA4C25 /* CommonBannerView.swift in Sources */, @@ -1003,7 +1007,7 @@ CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 5ZV937VB25; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = LS9CC38Y9L; ENABLE_USER_SCRIPT_SANDBOXING = NO; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = DolphinEnglishLearnStudent/Info.plist; @@ -1110,10 +1114,10 @@ "-ObjC", "-all_load", ); PRODUCT_BUNDLE_IDENTIFIER = com.sinata.dollearn.test; PRODUCT_BUNDLE_IDENTIFIER = com.sinata.dollearn; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = dev_dollearn; "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = dev_child; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; @@ -1138,7 +1142,7 @@ CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 5ZV937VB25; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = LS9CC38Y9L; ENABLE_USER_SCRIPT_SANDBOXING = NO; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = DolphinEnglishLearnStudent/Info.plist; @@ -1245,10 +1249,10 @@ "-ObjC", "-all_load", ); PRODUCT_BUNDLE_IDENTIFIER = com.sinata.dollearn.test; PRODUCT_BUNDLE_IDENTIFIER = com.sinata.dollearn; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = adhoc_dollearn; "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = adhoc_child; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; DolphinEnglishLearnStudent/Config/Config.swift
@@ -15,6 +15,8 @@ let WeChatAPPID = "wx723c6b080f204773" let WeChatSecrect = "b7904c5721aeccc20fd11fe9afc23f4d" let ProductMemberID = "com.dollearn.member.year.1" let ShareAppleKey = "af37e916cd2b4a0293e37ac405ba4f1c" var sceneDelegate:SceneDelegate? = { var uiScreen:UIScene? DolphinEnglishLearnStudent/Models/CommonModel.swift
@@ -445,3 +445,8 @@ var time = 0 var amount = 0 } struct PaymentInfoModel:HandyJSON{ var id = 0 var orderId = 0 } DolphinEnglishLearnStudent/Moudle/Me/MeVC.swift
@@ -59,6 +59,7 @@ self.imge_cover.sd_setImage(with: URL(string: model.headImg)) self.label_name.text = model.name items.append("剩余积分:\(model.integral)") UserViewModel.saveUserInfo(result.data!) } if let model = result.data?.userStudy{ @@ -113,8 +114,9 @@ @IBAction func quitAction(_ sender: UIButton) { CommonAlertView.show(content: "确认退出当前账户吗?") { sceneDelegate?.needLogin() Services.logoutStudy().subscribe(onNext: {result in sceneDelegate?.needLogin() }).disposed(by: self.disposeBag) } } DolphinEnglishLearnStudent/Moudle/Me/VC/VIPCenterVC.swift
@@ -7,14 +7,17 @@ import UIKit import WebKit import StoreKit class VIPCenterVC: BaseVC { private var btn_vip:UIButton! private var webView:WKWebView! private var inpurchaseManager = InPurchaseManager.instance() private var products = Set<SKProduct>() override func viewDidLoad() { super.viewDidLoad() override func viewDidLoad() { super.viewDidLoad() yy_popBlock = {[weak self] () in guard let weakSelf = self else { return } @@ -37,7 +40,12 @@ self.webView.loadHTMLString(model.info.jq_wrapHtml(edge: UIEdgeInsets(top: 0, left: 10, bottom: 3, right: 0)), baseURL: nil) } }).disposed(by: disposeBag) } inpurchaseManager.test1() inpurchaseManager.setProductList([ProductMemberID]) {[weak self] m in self?.products = m } } override func setUI() { @@ -67,8 +75,52 @@ } @objc func becomeVIPAction(){ ParentVerifiyView.show { ParentVerifiyView.show {[unowned self] in guard let userId = UserViewModel.getUserInfo()?.user?.id.string else {alert(msg: "请先登录");return} guard let product = products.first else {alert(msg: "获取内购失败");return} Services.orderStudent(count: 12, price: product.price.doubleValue).subscribe(onNext: {[unowned self] data in if let m = data.data{ showHUD("正在购买") InPurchaseManager.purchaseProduct(ID: ProductMemberID, applicationUsername: userId) {[unowned self] model in let transactionIdentifier = model.receipt?.in_app.first?.original_transaction_id ?? "" hiddenHUD() Services.pay(orderId: m.orderId, transactionIdentifier: transactionIdentifier).subscribe(onNext: {[unowned self] data in var count = 0 showHUD("正在查询支付结果") let t = Timer(timeInterval: 2.0, repeats: true) {[unowned self] timer in Services.queryOrderState(orderId: m.orderId).subscribe(onNext: {[unowned self]status in if status.data == true{ alertSuccess(msg: "购买成功") DispatchQueue.main.asyncAfter(deadline: .now()+1) {[unowned self] in navigationController?.popViewController() NotificationCenter.default.post(name: MeUserInfoUpdate_Noti, object: nil) } timer.invalidate() return } count += 1 if count >= 15{ hiddenHUD() print("循环结束") showHUD("查询结果失败,请联系客服") timer.invalidate() } },onError: {error in timer.invalidate() }).disposed(by: disposeBag) } t.fire() RunLoop.current.add(t, forMode: .common) }).disposed(by: disposeBag) } errorClouse: { error in hiddenHUD() alert(msg: error.localizedDescription) } } }).disposed(by: disposeBag) } } } DolphinEnglishLearnStudent/Services/InPurchaseManager.swift
New file @@ -0,0 +1,306 @@ // // StoreTransaction.swift // ExplorerProject // // Created by 无故事王国 on 2024/4/1. // Copyright © 2024 younger_times. All rights reserved. // import SwiftyStoreKit import StoreKit import HandyJSON struct PurchaseModel:HandyJSON{ var environment: String? var receipt:PurchaseReceiptModel? var status: Int = 0 } struct PurchaseReceiptModel:HandyJSON{ var adam_id: Int = 0 var app_item_id: Int = 0 var application_version: Int = 0 var bundle_id: String? var download_id: Int = 0 var in_app = [PurchageInAppModel]() var original_application_version: String? var original_purchase_date: String? var original_purchase_date_ms: Int = 0 var original_purchase_date_pst: String? var receipt_creation_date: String? var receipt_creation_date_ms: Int = 0 var receipt_creation_date_pst: String? var receipt_type: String? var request_date: String? var request_date_ms: Int = 0 var request_date_pst: String? var version_external_identifier: Int = 0 } struct PurchageInAppModel:HandyJSON{ } struct PurchaseResultModel:HandyJSON{ var environment: String? var receipt: PurchaseReceipt? var status: Int = 0 } struct PurchaseReceipt:HandyJSON{ var adam_id: Int = 0 var app_item_id: Int = 0 var application_version: String? var bundle_id: String? var download_id: Int = 0 var in_app = [PurchaseInAPP]() var original_application_version: String? var original_purchase_date: String? var original_purchase_date_ms: String? var original_purchase_date_pst: String? var receipt_creation_date: String? var receipt_creation_date_ms: String? var receipt_creation_date_pst: String? var receipt_type: String? var request_date: String? var request_date_ms: String? var request_date_pst: String? var version_external_identifier: Int = 0 } struct PurchaseInAPP:HandyJSON{ var in_app_ownership_type: String? var is_trial_period: String? var original_purchase_date: String? var original_purchase_date_ms: String? var original_purchase_date_pst: String? var original_transaction_id: String? var product_id: String? var purchase_date: String? var purchase_date_ms: String? var purchase_date_pst: String? var quantity: String? var transaction_id: String? } class InPurchaseManager:NSObject{ private static var _sharedInstance: InPurchaseManager! private var productList = Set<String>() private(set) var products = Set<SKProduct>() @discardableResult public class func instance() -> InPurchaseManager { guard let instance = _sharedInstance else { _sharedInstance = InPurchaseManager() return _sharedInstance! } return instance } func setProductList(_ list:Set<String>,clouse:@escaping (Set<SKProduct>)->Void){ productList = list SwiftyStoreKit.retrieveProductsInfo(list) { result in if result.retrievedProducts.count > 0 { InPurchaseManager._sharedInstance.products = result.retrievedProducts clouse(result.retrievedProducts) } } } // func getIPAPrice(_ price:Int,clouse:@escaping (SKProduct)->Void){ // var ipaId = "" // switch price { // case 98: ipaId = "com.jkfitness.a.price.1" // case 298:ipaId = "com.jkfitness.a.price.2" // case 488:ipaId = "com.jkfitness.a.price.3" // case 698:ipaId = "com.jkfitness.a.price.4" // case 998:ipaId = "com.jkfitness.a.price.5" // default:break // } // // var productIds = Set<String>() // productIds.insert(ipaId) // // InPurchaseManager.instance().setProductList(productIds) {products in // if let product = products.first{ // clouse(product) // } // } // } func dismiss(){ InPurchaseManager._sharedInstance = nil } /// 购买 /// - Parameters: /// - ID: ID值 /// - quantity: 购买数量 /// - applicationUsername: 购买用户名的唯一标识 class func purchaseProduct(ID:String,quantity:Int = 1,applicationUsername:String, completion: @escaping ( PurchaseResultModel) -> Void,errorClouse:@escaping (Error)->Void){ var purcahseProduct:SKProduct? for product in _sharedInstance.products { if product.productIdentifier == ID{ purcahseProduct = product;break } } guard let product = purcahseProduct else {errorClouse(NSError(domain: "Empty Product ID", code: 1000));return} var inSanbox:Bool = false #if DEBUG inSanbox = true #endif SwiftyStoreKit.completeTransactions { purchases in if let product = purchases.first{ switch product.transaction.transactionState { case .purchased: alertSuccess(msg: "完成购买") case .purchasing: showHUD("购买中") case .failed: alertError(msg: "购买失败") case .restored: alertSuccess(msg: "恢复购买") default:break } } } SwiftyStoreKit.purchaseProduct(product, quantity: quantity, atomically: true, applicationUsername: applicationUsername, simulatesAskToBuyInSandbox: false, paymentDiscount: nil) { result in switch result { case .success(let purchase): var type:AppleReceiptValidator.VerifyReceiptURLType = .production if inSanbox{ type = .sandbox } let recepit = AppleReceiptValidator(service: type, sharedSecret: nil) SwiftyStoreKit.verifyReceipt(using: recepit) { result in switch result { case .success(let receipt): if let model = PurchaseResultModel.deserialize(from: receipt){ completion(model) } break case .error(let error): errorClouse(error) // print(error.localizedDescription) } } case .error(let error): // print(error.localizedDescription) errorClouse(error) } } } /// 恢复购买 class func resotrePurchase(applicationUsername:String, completion: @escaping ( PurchaseResultModel) -> Void,errorClouse:@escaping (Error)->Void){ SwiftyStoreKit.completeTransactions { purchases in if let product = purchases.first{ switch product.transaction.transactionState { case .purchased: alertSuccess(msg: "完成购买") case .purchasing: showHUD("购买中") case .failed: alertError(msg: "购买失败") case .restored: alertSuccess(msg: "恢复购买") default:break } } } SwiftyStoreKit.restorePurchases(atomically: true, applicationUsername: applicationUsername) { result in if result.restoreFailedPurchases.count > 0 { //恢复失败 print("restore Failed:\(result.restoredPurchases)") }else if result.restoredPurchases.count > 0 { print("restore Success") var inSanbox:Bool = false #if DEBUG inSanbox = true #endif var type:AppleReceiptValidator.VerifyReceiptURLType = .production if inSanbox{ type = .sandbox } let recepit = AppleReceiptValidator(service: type, sharedSecret: nil) SwiftyStoreKit.verifyReceipt(using: recepit) { result in switch result { case .success(let receipt): if let model = PurchaseResultModel.deserialize(from: receipt){ completion(model) } break case .error(let error): errorClouse(error) } } }else{ print("Nothing to Restore") } } } //获取APP 首次安装时间 func test1(){ let queue = DispatchQueue(label: "inapppurchasequeue") queue.async { let receiptURL = Bundle.main.appStoreReceiptURL guard receiptURL?.path != nil else { return } do { let receiptData = try Data(contentsOf: receiptURL!, options: .alwaysMapped) let base64EncodedReceipt = receiptData.base64EncodedString(options: []) var verifyReceipt:String = AppleReceiptValidator.VerifyReceiptURLType.sandbox.rawValue #if !DEBUG verifyReceipt = AppleReceiptValidator.VerifyReceiptURLType.production.rawValue #endif let purchaseURL = URL(string: verifyReceipt)! var request = URLRequest(url: purchaseURL) request.httpMethod = "POST" request.setValue("application/json", forHTTPHeaderField: "Content-Type") let jsonBody: [String: Any] = [ "receipt-data": base64EncodedReceipt, "password": ShareAppleKey // 你的共享密钥 ] let jsonData = try JSONSerialization.data(withJSONObject: jsonBody, options: []) request.httpBody = jsonData let session = URLSession.shared let task = session.dataTask(with: request) { (data, response, error) in if let error = error {return} guard let data = data else { return } do { if let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] { print("获取内购信息成功") print(json) if let purchageModel = PurchaseModel.deserialize(from: json),purchageModel.status == 0{ } } } catch { print("JSON processing failed: \(error)") } } task.resume() } catch { print("Error reading receipt data【凭证不存在】: \(error)") } } } } DolphinEnglishLearnStudent/Services/NetworkRequest.swift
@@ -209,7 +209,7 @@ sharedSessionManager.request(params.url.absoluteString, method: method, parameters:params.done(), encoding: newEncoding, headers:headers).validate().responseData{response in LogInfo("请求地址:\(params.url)") LogInfo("请求参数:\(params.params)") hiddenHUD() if progress{hiddenHUD()} guard response.error == nil else { LogError("\(response.error!)") DolphinEnglishLearnStudent/Services/Services.swift
@@ -402,6 +402,37 @@ params.interface(url: "/study/base/user/deleteUserStudy") return NetworkRequest.request(params: params, method: .post, progress: true) } /// 支付 /// - Parameters: /// - count: 月份,12 /// - price: 价格 class func orderStudent(count:Int,price:Double,payType:Int = 3)->Observable<BaseResponse<PaymentInfoModel>>{ let params = ParamsAppender.build(url: All_Url) params.interface(url: "/study/base/user/orderStudent") .append(key: "count", value: count) .append(key: "price", value: price) .append(key: "payType", value: payType) return NetworkRequest.request(params: params, method: .post, progress: true) } class func pay(orderId:Int,transactionIdentifier:String,payType:Int = 3)->Observable<BaseResponse<SimpleModel>>{ let params = ParamsAppender.build(url: All_Url) params.interface(url: "/study/base/user/pay") .append(key: "orderId", value: orderId) .append(key: "transactionIdentifier", value: transactionIdentifier) .append(key: "payType", value: payType) return NetworkRequest.request(params: params, method: .post,encoding: JSONEncoding.default, progress: true) } class func queryOrderState(orderId:Int)->Observable<BaseResponse<Bool>>{ let params = ParamsAppender.build(url: All_Url) params.interface(url: "/study/base/user/queryOrderState") .append(key: "orderId", value: orderId) return NetworkRequest.request(params: params, method: .post, progress: false) } } extension Services{ DolphinEnglishLearnStudent/ViewModel/UserViewModel.swift
@@ -18,6 +18,7 @@ } struct UserInfoUserModel:HandyJSON,Codable{ var id = 0 var account: String = "" var birthday: String = "" var createBy: String = "" Podfile
@@ -16,5 +16,5 @@ pod 'Alamofire' # 网络请求框架 pod 'Lantern' # 图片浏览器 pod 'WechatOpenSDK-XCFramework' # 微信开放平台组件 pod 'SwiftyStoreKit' end