From e2f63b8a1642b90725d070c2ff2b6d12bd3f041f Mon Sep 17 00:00:00 2001 From: zeng Date: Sat, 9 May 2026 13:27:20 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8A=A0=E5=85=A5=E5=A4=B1=E8=B4=A5=E6=97=A5?= =?UTF-8?q?=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../InAppPurchseView/APUploadIAPListVC.swift | 130 +++++++++++++++++- 1 file changed, 126 insertions(+), 4 deletions(-) diff --git a/AppleParty/AppleParty/AppListView/InAppPurchseView/APUploadIAPListVC.swift b/AppleParty/AppleParty/AppListView/InAppPurchseView/APUploadIAPListVC.swift index 788286f..226df81 100644 --- a/AppleParty/AppleParty/AppListView/InAppPurchseView/APUploadIAPListVC.swift +++ b/AppleParty/AppleParty/AppListView/InAppPurchseView/APUploadIAPListVC.swift @@ -27,6 +27,15 @@ class APUploadIAPListVC: NSViewController { } private var screenshotPaths = [String: String]() + private var uploadFailureSummaries: [String: UploadFailureSummary] = [:] + private var uploadFailureOrder: [String] = [] + + private struct UploadFailureSummary { + let productId: String + let productName: String + let productType: String + var reasons: [String] + } override func viewDidLoad() { super.viewDidLoad() @@ -88,6 +97,8 @@ class APUploadIAPListVC: NSViewController { enterBtn.isEnabled = false APHUD.show(message: "上传中", view: self.view) + uploadFailureSummaries.removeAll() + uploadFailureOrder.removeAll() let uploadIAPs: ((AppStoreConnectKey) -> Void) = { [weak self] ascKey in // 上传数据 self?.updateInAppPurchse(iaps: list, appId: appid, ascKey: ascKey) @@ -169,12 +180,45 @@ extension APUploadIAPListVC { } } + self.outputUploadFailureSummary(ascAPI: ascAPI) self.enterBtn.isEnabled = true APHUD.hide() ascAPI.addMessage("完成全部内购商品,可稍后在苹果后台查看!✅✅✅") } } + func recordUploadFailure(product: IAPProduct, reason: String) { + let key = product.productId + let typeName = product.inAppPurchaseType.CNValue() + if uploadFailureSummaries[key] == nil { + uploadFailureSummaries[key] = UploadFailureSummary( + productId: product.productId, + productName: product.name, + productType: typeName, + reasons: [] + ) + uploadFailureOrder.append(key) + } + + guard var summary = uploadFailureSummaries[key] else { return } + if summary.reasons.contains(reason) == false { + summary.reasons.append(reason) + } + uploadFailureSummaries[key] = summary + } + + func outputUploadFailureSummary(ascAPI: APASCAPI) { + guard uploadFailureOrder.isNotEmpty else { return } + + ascAPI.addMessage("本次上传失败的支付项如下:") + for key in uploadFailureOrder { + guard let summary = uploadFailureSummaries[key] else { continue } + let reasonText = summary.reasons.joined(separator: ";") + ascAPI.addMessage( + "支付项失败:\(summary.productId)|\(summary.productName)|\(summary.productType)|\(reasonText)") + } + } + // MARK: - 上传内购类型商品 /// 创建内购商品 @@ -243,6 +287,7 @@ extension APUploadIAPListVC { guard let iap = await ascAPI.createInAppPurchases(appId: appId, product: product) else { // 创建失败 ascAPI.addMessage("内购商品:\(product.productId) ,创建失败!❌ ") + recordUploadFailure(product: product, reason: "内购项创建失败") return } @@ -269,6 +314,7 @@ extension APUploadIAPListVC { func updateIAPPricePoint(iapId: String, product: IAPProduct, ascAPI: APASCAPI) async { guard let schedule = product.priceSchedules else { ascAPI.addMessage("无价格计划表:\(product.productId) ,请确认!❌ ") + recordUploadFailure(product: product, reason: "价格计划表不存在") return } @@ -311,6 +357,9 @@ extension APUploadIAPListVC { index: included.count)) } else { ascAPI.addMessage("自定价格的内购价格点:\(territory),\(customerPrice) ,未找到此档位!❌ ") + recordUploadFailure( + product: product, + reason: "自定价格未找到价格点:\(territory) / \(customerPrice)") } } @@ -325,10 +374,16 @@ extension APUploadIAPListVC { } else { // 价格档位配置失败 ascAPI.addMessage("内购价格点:\(baseTerritory),\(baseCustomerPrice) ,更新价格失败!❌ ") + recordUploadFailure( + product: product, + reason: "价格档位配置失败:\(baseTerritory) / \(baseCustomerPrice)") } } else { // 找不到价格档位 ascAPI.addMessage("基准国家的内购价格点:\(baseTerritory),\(baseCustomerPrice) ,未找到此档位!❌ ") + recordUploadFailure( + product: product, + reason: "基准国家价格点未找到:\(baseTerritory) / \(baseCustomerPrice)") } } @@ -345,6 +400,9 @@ extension APUploadIAPListVC { } else { // 本地化语言配置失败 ascAPI.addMessage("内购本地化版本:\(localization.locale) ,更新语言失败!❌ ") + recordUploadFailure( + product: product, + reason: "本地化创建失败:\(localization.locale)") } } @@ -354,6 +412,7 @@ extension APUploadIAPListVC { let imgName = product.reviewScreenshot guard let imgPath = screenshotPaths[imgName] else { ascAPI.addMessage("内购商品:\(product.productId) 无送审截图或未上传截图~") + recordUploadFailure(product: product, reason: "未上传送审截图") return } @@ -361,6 +420,7 @@ extension APUploadIAPListVC { let uploadFileName = imaUrl.lastPathComponent.isEmpty ? imgName : imaUrl.lastPathComponent guard let fileMD5 = URL.init(fileURLWithPath: imgPath).fileMD5() else { ascAPI.addMessage("内购商品截图文件错误:\(imgPath) ,无法生成 md5 值~") + recordUploadFailure(product: product, reason: "截图文件无法生成 MD5") return } @@ -371,6 +431,7 @@ extension APUploadIAPListVC { let status = await ascAPI.deleteInAppPurchasesScreenshot(iapShotId: ost.id) if status != 204 { ascAPI.addMessage("内购商品截图创建失败:\(imgName) ,无法删除旧截图~") + recordUploadFailure(product: product, reason: "送审截图删除旧图失败") } } @@ -379,6 +440,7 @@ extension APUploadIAPListVC { let imaSize = imaUrl.fileSizeInt() guard imaSize > 0 else { ascAPI.addMessage("内购商品截图文件错误:\(imgPath) ,文件大小为 0~") + recordUploadFailure(product: product, reason: "截图文件大小为 0") return } guard @@ -387,6 +449,7 @@ extension APUploadIAPListVC { else { // 创建失败 ascAPI.addMessage("内购商品:\(product.productId) ,创建送审截图失败!❌ ") + recordUploadFailure(product: product, reason: "送审截图创建失败") return } @@ -397,6 +460,7 @@ extension APUploadIAPListVC { let baseURL = URL(string: url) else { ascAPI.addMessage("内购商品:\(product.productId) ,创建送审截图失败!苹果参数异常~ ❌ ") + recordUploadFailure(product: product, reason: "送审截图上传参数异常") return } @@ -411,12 +475,14 @@ extension APUploadIAPListVC { guard let response = try? await URLSession.shared.upload(for: request, fromFile: imaUrl) else { ascAPI.addMessage("内购商品:\(product.productId) ,创建送审截图失败!上传图片异常~ ❌ ") + recordUploadFailure(product: product, reason: "送审截图上传异常") return } guard let responseCode = (response.1 as? HTTPURLResponse)?.statusCode, responseCode == 200 else { ascAPI.addMessage( "内购商品:\(product.productId) ,创建送审截图失败!上传图片异常 \(response.1.description)~ ❌ ") + recordUploadFailure(product: product, reason: "送审截图上传响应异常") return } @@ -428,6 +494,7 @@ extension APUploadIAPListVC { ascAPI.addMessage("内购商品:\(product.productId) ,送审截图上传成功!✅ ") } else { ascAPI.addMessage("内购商品:\(product.productId) ,送审截图可能上传失败! ") + recordUploadFailure(product: product, reason: "送审截图确认失败") } } @@ -456,9 +523,11 @@ extension APUploadIAPListVC { ascAPI.addMessage("选择:所有国家(地区)销售,\(newTerritory),更新成功!✅ ") } else { ascAPI.addMessage("选择:所有国家(地区)销售,\(newTerritory),更新失败!❌ ") + recordUploadFailure(product: product, reason: "销售国家/地区更新失败") } } else { ascAPI.addMessage("选择:所有国家(地区)销售,\(newTerritory),无法设置!获取国家标识码失败!❌ ") + recordUploadFailure(product: product, reason: "销售国家/地区列表获取失败") } return } @@ -481,6 +550,7 @@ extension APUploadIAPListVC { ascAPI.addMessage("内购商品的销售国家/地区:\(customerTerritory) ,更新成功!✅ ") } else { ascAPI.addMessage("内购商品的销售国家/地区:\(customerTerritory) ,更新失败!❌ ") + recordUploadFailure(product: product, reason: "销售国家/地区更新失败") } } @@ -514,18 +584,32 @@ extension APUploadIAPListVC { subGroups.append(group) for localization in product.localizations { - let _ = await ascAPI.createSubscriptionGroupLocalizations( + if await ascAPI.createSubscriptionGroupLocalizations( iapGroupId: group.id, name: localization.name, locale: localization.locale, - customAppName: nil) + customAppName: nil) == nil + { + recordUploadFailure( + product: product, + reason: "订阅组本地化创建失败:\(localization.locale)") + } } + } else if currentSubGroup == nil { + ascAPI.addMessage("订阅商品:\(product.productId) ,创建订阅组失败!❌ ") + recordUploadFailure(product: product, reason: "订阅组创建失败") + return } //订阅组设置国际化 if let group = currentSubGroup { if let localization = product.groupLocalization { - let _ = await ascAPI.createSubscriptionGroupLocalizations( + if await ascAPI.createSubscriptionGroupLocalizations( iapGroupId: group.id, name: localization.name, locale: localization.locale, - customAppName: nil) + customAppName: nil) == nil + { + recordUploadFailure( + product: product, + reason: "订阅组本地化更新失败:\(localization.locale)") + } } } @@ -549,6 +633,7 @@ extension APUploadIAPListVC { guard let iap = await ascAPI.updateSubscription(iapId: sub.id, product: product) else { // 修改失败 ascAPI.addMessage("订阅商品已经存在:\(product.productId) ,更新信息失败!❌ ") + recordUploadFailure(product: product, reason: "订阅项更新失败") return } @@ -571,6 +656,9 @@ extension APUploadIAPListVC { } else { // 本地化语言更新失败 ascAPI.addMessage("订阅商品本地化版本:\(localization.locale) ,更新语言失败!❌ ") + recordUploadFailure( + product: product, + reason: "订阅本地化更新失败:\(localization.locale)") } } else { // 创建 @@ -603,6 +691,7 @@ extension APUploadIAPListVC { else { // 创建失败 ascAPI.addMessage("订阅商品:\(product.productId) ,创建失败!❌ ") + recordUploadFailure(product: product, reason: "订阅项创建失败") return } @@ -675,6 +764,7 @@ extension APUploadIAPListVC { } } else { ascAPI.addMessage("订阅商品:\(product.productId) ,提交审核失败!请检查商品是否为准备提交状态,或是否需要随 App 版本提交。❌ ") + recordUploadFailure(product: product, reason: "订阅提交审核失败") } } @@ -682,6 +772,7 @@ extension APUploadIAPListVC { func updateSubscriptionPricePoint(iapId: String, product: IAPProduct, ascAPI: APASCAPI) async { guard let schedule = product.priceSchedules else { ascAPI.addMessage("无价格计划表:\(product.productId) ,请确认!❌ ") + recordUploadFailure(product: product, reason: "价格计划表不存在") return } @@ -722,9 +813,15 @@ extension APUploadIAPListVC { ascAPI.addMessage("自定价格的订阅商品的价格点:\(territory),\(customerPrice) ,更新价格成功!✅ ") } else { ascAPI.addMessage("自定价格的订阅商品的价格点:\(territory),\(customerPrice) ,更新价格失败!❌ ") + recordUploadFailure( + product: product, + reason: "自定价格更新失败:\(territory) / \(customerPrice)") } } else { ascAPI.addMessage("自定价格的订阅商品价格点:\(territory),\(customerPrice) ,未找到此档位!❌ ") + recordUploadFailure( + product: product, + reason: "自定价格未找到价格点:\(territory) / \(customerPrice)") } } @@ -748,11 +845,17 @@ extension APUploadIAPListVC { } else { // 价格档位配置失败 ascAPI.addMessage("全球均衡价格的订阅商品的价格点:\(territory),\(customerPrice) ,更新价格失败!❌ ") + recordUploadFailure( + product: product, + reason: "全球均衡价格更新失败:\(territory) / \(customerPrice)") } } } else { // 找不到价格档位 ascAPI.addMessage("基准国家的订阅商品价格点:\(baseTerritory),\(baseCustomerPrice) ,未找到此档位!❌ ") + recordUploadFailure( + product: product, + reason: "基准国家价格点未找到:\(baseTerritory) / \(baseCustomerPrice)") } } @@ -769,6 +872,9 @@ extension APUploadIAPListVC { } else { // 本地化语言配置失败 ascAPI.addMessage("订阅商品本地化版本:\(localization.locale) ,更新语言失败!❌ ") + recordUploadFailure( + product: product, + reason: "订阅本地化创建失败:\(localization.locale)") } } @@ -778,6 +884,7 @@ extension APUploadIAPListVC { guard (await ascAPI.updateSubscription(iapId: iapId, product: product)) != nil else { ascAPI.addMessage("订阅商品:\(product.productId) ,刷新基础元数据失败!❌ ") + recordUploadFailure(product: product, reason: "订阅元数据刷新失败") return } @@ -801,6 +908,9 @@ extension APUploadIAPListVC { ascAPI.addMessage("订阅商品:\(product.productId) ,元数据状态刷新成功!✅ ") } else { ascAPI.addMessage("订阅商品:\(product.productId) ,刷新本地化元数据失败!❌ ") + recordUploadFailure( + product: product, + reason: "订阅本地化元数据刷新失败:\(localization.locale)") } } @@ -810,6 +920,7 @@ extension APUploadIAPListVC { let imgName = product.reviewScreenshot guard let imgPath = screenshotPaths[imgName] else { ascAPI.addMessage("订阅商品:\(product.productId) 无送审截图或未上传截图~") + recordUploadFailure(product: product, reason: "未上传送审截图") return } @@ -817,6 +928,7 @@ extension APUploadIAPListVC { let uploadFileName = imaUrl.lastPathComponent.isEmpty ? imgName : imaUrl.lastPathComponent guard let fileMD5 = URL.init(fileURLWithPath: imgPath).fileMD5() else { ascAPI.addMessage("订阅商品截图文件错误:\(imgPath) ,无法生成 md5 值~") + recordUploadFailure(product: product, reason: "截图文件无法生成 MD5") return } @@ -827,6 +939,7 @@ extension APUploadIAPListVC { let status = await ascAPI.deleteSubscriptionScreenshot(iapShotId: ost.id) if status != 204 { ascAPI.addMessage("订阅商品截图创建失败:\(imgName) ,无法删除旧截图~") + recordUploadFailure(product: product, reason: "送审截图删除旧图失败") } } @@ -835,6 +948,7 @@ extension APUploadIAPListVC { let imaSize = imaUrl.fileSizeInt() guard imaSize > 0 else { ascAPI.addMessage("订阅商品截图文件错误:\(imgPath) ,文件大小为 0~") + recordUploadFailure(product: product, reason: "截图文件大小为 0") return } guard @@ -843,6 +957,7 @@ extension APUploadIAPListVC { else { // 创建失败 ascAPI.addMessage("订阅商品:\(product.productId) ,创建送审截图失败!❌ ") + recordUploadFailure(product: product, reason: "送审截图创建失败") return } @@ -853,6 +968,7 @@ extension APUploadIAPListVC { let baseURL = URL(string: url) else { ascAPI.addMessage("订阅商品:\(product.productId) ,创建送审截图失败!苹果参数异常~ ❌ ") + recordUploadFailure(product: product, reason: "送审截图上传参数异常") return } @@ -867,12 +983,14 @@ extension APUploadIAPListVC { guard let response = try? await URLSession.shared.upload(for: request, fromFile: imaUrl) else { ascAPI.addMessage("订阅商品:\(product.productId) ,创建送审截图失败!上传图片异常~ ❌ ") + recordUploadFailure(product: product, reason: "送审截图上传异常") return } guard let responseCode = (response.1 as? HTTPURLResponse)?.statusCode, responseCode == 200 else { ascAPI.addMessage( "订阅商品:\(product.productId) ,创建送审截图失败!上传图片异常 \(response.1.description)~ ❌ ") + recordUploadFailure(product: product, reason: "送审截图上传响应异常") return } @@ -883,6 +1001,7 @@ extension APUploadIAPListVC { ascAPI.addMessage("订阅商品:\(product.productId) ,送审截图上传成功!✅ ") } else { ascAPI.addMessage("订阅商品:\(product.productId) ,送审截图可能上传失败! ") + recordUploadFailure(product: product, reason: "送审截图确认失败") } } @@ -913,9 +1032,11 @@ extension APUploadIAPListVC { ascAPI.addMessage("选择:所有国家(地区)销售,\(newTerritory),更新成功!✅ ") } else { ascAPI.addMessage("选择:所有国家(地区)销售,\(newTerritory),更新失败!❌ ") + recordUploadFailure(product: product, reason: "销售国家/地区更新失败") } } else { ascAPI.addMessage("选择:所有国家(地区)销售,\(newTerritory),无法设置!获取国家标识码失败!❌ ") + recordUploadFailure(product: product, reason: "销售国家/地区列表获取失败") } return } @@ -938,6 +1059,7 @@ extension APUploadIAPListVC { ascAPI.addMessage("订阅商品的销售国家/地区:\(customerTerritory) ,更新成功!✅ ") } else { ascAPI.addMessage("订阅商品的销售国家/地区:\(customerTerritory) ,更新失败!❌ ") + recordUploadFailure(product: product, reason: "销售国家/地区更新失败") } } }