diff --git a/AppleParty/AppleParty/AppListView/InAppPurchseView/APUploadIAPListVC.swift b/AppleParty/AppleParty/AppListView/InAppPurchseView/APUploadIAPListVC.swift index 5524cef..79b805a 100644 --- a/AppleParty/AppleParty/AppListView/InAppPurchseView/APUploadIAPListVC.swift +++ b/AppleParty/AppleParty/AppListView/InAppPurchseView/APUploadIAPListVC.swift @@ -19,7 +19,7 @@ class APUploadIAPListVC: NSViewController { setupUI() } } - public var iaps = [IAPProduct]() { + public var iaps = [IAPProduct]() { didSet { self.tableView.reloadData() } @@ -43,9 +43,14 @@ class APUploadIAPListVC: NSViewController { func showUploadView() { // 不能同时 present 出来 DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) { - let mainStoryboard = NSStoryboard(name: "InAppPurchseView", bundle: Bundle(for: self.classForCoder)) - let upVC = mainStoryboard.instantiateController(withIdentifier: "IAPUploadVCID") as? IAPUploadImageVC - let screenshot = self.iaps.filter({ $0.reviewScreenshot.count > 0 }).map({ $0.reviewScreenshot }) + let mainStoryboard = NSStoryboard( + name: "InAppPurchseView", bundle: Bundle(for: self.classForCoder)) + let upVC = + mainStoryboard.instantiateController(withIdentifier: "IAPUploadVCID") + as? IAPUploadImageVC + let screenshot = self.iaps.filter({ $0.reviewScreenshot.count > 0 }).map({ + $0.reviewScreenshot + }) // 去重后的图片名 let uniquedshot = screenshot.enumerated().filter { (index, value) -> Bool in return screenshot.firstIndex(of: value) == index @@ -75,7 +80,6 @@ class APUploadIAPListVC: NSViewController { return } - guard let appid = currentApp?.appId else { APHUD.hide(message: "当前 App 的 appleid 为空!", delayTime: 1) return @@ -91,7 +95,7 @@ class APUploadIAPListVC: NSViewController { guard let ascKey = InfoCenter.shared.currentASCKey else { let vc = APASCKeysSettingVC() vc.updateCompletion = { password in - if let ascKey = password { + if let ascKey = password { uploadIAPs(ascKey) } } @@ -109,10 +113,11 @@ extension APUploadIAPListVC { func updateInAppPurchse(iaps: [IAPProduct], appId: String, ascKey: AppStoreConnectKey) { let showApiRateLimit = showApiRateLimitLogsBtn.state.rawValue == 1 - let ascAPI = APASCAPI.init(issuerID: ascKey.issuerID, - privateKeyID: ascKey.privateKeyID, - privateKey: ascKey.privateKey, - showApiRateLimit: showApiRateLimit) + let ascAPI = APASCAPI.init( + issuerID: ascKey.issuerID, + privateKeyID: ascKey.privateKeyID, + privateKey: ascKey.privateKey, + showApiRateLimit: showApiRateLimit) ascAPI.addMessage("密钥信息:\(ascKey.issuerID), \(ascKey.privateKeyID), \(ascKey.privateKey)") Task { @@ -152,7 +157,8 @@ extension APUploadIAPListVC { if product.inAppPurchaseType == .AUTO_RENEWABLE { await createRenewSubscription(appId: appId, product: product, ascAPI: ascAPI) } else { - await createInAppPurchase(appId: appId, product: product, oldIAPs: oldIAPs, ascAPI: ascAPI) + await createInAppPurchase( + appId: appId, product: product, oldIAPs: oldIAPs, ascAPI: ascAPI) } } @@ -165,13 +171,15 @@ extension APUploadIAPListVC { // MARK: - 上传内购类型商品 /// 创建内购商品 - func createInAppPurchase(appId: String, product: IAPProduct, oldIAPs: [ASCInAppPurchaseV2], ascAPI: APASCAPI) async { + func createInAppPurchase( + appId: String, product: IAPProduct, oldIAPs: [ASCInAppPurchaseV2], ascAPI: APASCAPI + ) async { ascAPI.addMessage("开始上传内购商品:\(product.productId),\(product.name) ") // 检查是否已经存在此商品,如果存在就修改信息,如果不存在就创建 let iaps = oldIAPs.filter({ $0.attributes?.productID == product.productId }) if let iap = iaps.first { ascAPI.addMessage("订阅商品已经存在:\(product.productId) ,跳过更新信息...") - return; + return ascAPI.addMessage("内购已经存在:\(product.productId) ,开始更新信息中...") // 0. 审核备注如果原来有值,而新字段无值,则使用原值 @@ -180,23 +188,27 @@ extension APUploadIAPListVC { product.reviewNote = note } // 1.修改原商品信息 - guard let iap = await ascAPI.updateInAppPurchases(iapId: iap.id, product: product) else { + guard let iap = await ascAPI.updateInAppPurchases(iapId: iap.id, product: product) + else { // 修改失败 ascAPI.addMessage("内购已经存在:\(product.productId) ,更新信息失败!❌ ") return } - // 3. 商品本地化语言 ascAPI.addMessage("开始更新内购本地化版本:\(product.productId)") let localizations = await ascAPI.fetchInAppPurchasesLocalizations(iapId: iap.id) for localization in product.localizations { // 如果已经存在本地化语言,则更新 - if let locale = localizations.filter({ $0.attributes?.locale == localization.locale }).first { + if let locale = localizations.filter({ + $0.attributes?.locale == localization.locale + }).first { // 更新 ascAPI.addMessage("内购已存在本地化版本:\(localization.locale),开始更新信息中...") - if (await ascAPI.updateInAppPurchasesLocalization(iapLocaleId: locale.id, localization: localization)) != nil { + if (await ascAPI.updateInAppPurchasesLocalization( + iapLocaleId: locale.id, localization: localization)) != nil + { // 本地化语言更新成功 ascAPI.addMessage("内购本地化版本:\(localization.locale) ,更新语言成功!✅ ") } else { @@ -205,7 +217,8 @@ extension APUploadIAPListVC { } } else { // 创建 - await createIAPLocalization(iapId: iap.id, localization: localization, product: product, ascAPI: ascAPI) + await createIAPLocalization( + iapId: iap.id, localization: localization, product: product, ascAPI: ascAPI) } } @@ -226,10 +239,10 @@ extension APUploadIAPListVC { return } - // 3. 商品本地化语言 for localization in product.localizations { - await createIAPLocalization(iapId: iap.id, localization: localization, product: product, ascAPI: ascAPI) + await createIAPLocalization( + iapId: iap.id, localization: localization, product: product, ascAPI: ascAPI) } // 4. 商品截图 @@ -258,23 +271,37 @@ extension APUploadIAPListVC { ascAPI.addMessage("开始更新价格计划表:\(product.productId),\(baseTerritory),\(baseCustomerPrice) \n") let points = await ascAPI.fetchPricePoints(iapId: iapId, territory: [baseTerritory]) - if let point = points.filter({ $0.attributes?.customerPrice!.normalizePrice() == baseCustomerPrice }).first { + if let point = points.filter({ + $0.attributes?.customerPrice!.normalizePrice() == baseCustomerPrice + }).first { var manualPrices: [Any] = [] var included: [Any] = [] ascAPI.addMessage("开始构建基准国家和自定价格:") // base Territory - manualPrices.append(["id": "${\(baseTerritory)-\(included.count)}", "type": "inAppPurchasePrices"]) - included.append(ascAPI.fetchInAppPurchasePriceSchedule(scheduleId: baseTerritory, pricePointId: point.id, iapId: iapId, index: included.count)) + manualPrices.append([ + "id": "${\(baseTerritory)-\(included.count)}", "type": "inAppPurchasePrices", + ]) + included.append( + ascAPI.fetchInAppPurchasePriceSchedule( + scheduleId: baseTerritory, pricePointId: point.id, iapId: iapId, + index: included.count)) // customerPrice for pricePoint in schedule.manualPrices { let territory = pricePoint.territory let customerPrice = pricePoint.customerPrice.normalizePrice() let points = await ascAPI.fetchPricePoints(iapId: iapId, territory: [territory]) - if let point = points.filter({ $0.attributes?.customerPrice!.normalizePrice() == customerPrice }).first { - manualPrices.append(["id": "${\(territory)-\(included.count)}", "type": "inAppPurchasePrices"]) - included.append(ascAPI.fetchInAppPurchasePriceSchedule(scheduleId: territory, pricePointId: point.id, iapId: iapId, index: included.count)) + if let point = points.filter({ + $0.attributes?.customerPrice!.normalizePrice() == customerPrice + }).first { + manualPrices.append([ + "id": "${\(territory)-\(included.count)}", "type": "inAppPurchasePrices", + ]) + included.append( + ascAPI.fetchInAppPurchasePriceSchedule( + scheduleId: territory, pricePointId: point.id, iapId: iapId, + index: included.count)) } else { ascAPI.addMessage("自定价格的内购价格点:\(territory),\(customerPrice) ,未找到此档位!❌ ") } @@ -282,7 +309,10 @@ extension APUploadIAPListVC { ascAPI.saveLogs(log: "内购的基准国家和自定价格:\(manualPrices),\(included)") - if (await ascAPI.updateInAppPurchasePricePoint(iapId: iapId, baseTerritoryId: baseTerritory, manualPrices: manualPrices, included: included)) != nil { + if (await ascAPI.updateInAppPurchasePricePoint( + iapId: iapId, baseTerritoryId: baseTerritory, manualPrices: manualPrices, + included: included)) != nil + { // 价格档位配置成功 ascAPI.addMessage("内购价格点:\(baseTerritory),\(baseCustomerPrice) ,更新价格成功!✅ ") } else { @@ -295,11 +325,14 @@ extension APUploadIAPListVC { } } - /// 创建内购商品本地化信息 - func createIAPLocalization(iapId: String, localization: IAPLocalization, product: IAPProduct, ascAPI: APASCAPI) async { + func createIAPLocalization( + iapId: String, localization: IAPLocalization, product: IAPProduct, ascAPI: APASCAPI + ) async { ascAPI.addMessage("开始更新本地化版本:\(product.productId),\(localization.locale)") - if (await ascAPI.createInAppPurchasesLocalization(iapId: iapId, localization: localization)) != nil { + if (await ascAPI.createInAppPurchasesLocalization(iapId: iapId, localization: localization)) + != nil + { // 本地化语言配置成功 ascAPI.addMessage("内购本地化版本:\(localization.locale) ,更新语言成功!✅ ") } else { @@ -341,7 +374,10 @@ extension APUploadIAPListVC { ascAPI.addMessage("内购商品截图文件错误:\(imgPath) ,文件大小为 0~") return } - guard let shot = await ascAPI.createInAppPurchasesScreenshot(iapId: iapId, fileName: uploadFileName, fileSize: imaSize) else { + guard + let shot = await ascAPI.createInAppPurchasesScreenshot( + iapId: iapId, fileName: uploadFileName, fileSize: imaSize) + else { // 创建失败 ascAPI.addMessage("内购商品:\(product.productId) ,创建送审截图失败!❌ ") return @@ -349,12 +385,13 @@ extension APUploadIAPListVC { // 根据苹果接口返回的上传接口上传 guard let method = shot.attributes?.uploadOperations?.first?.method, - let url = shot.attributes?.uploadOperations?.first?.url, - let requestHeaders = shot.attributes?.uploadOperations?.first?.requestHeaders, - let baseURL = URL(string: url) else { - ascAPI.addMessage("内购商品:\(product.productId) ,创建送审截图失败!苹果参数异常~ ❌ ") - return - } + let url = shot.attributes?.uploadOperations?.first?.url, + let requestHeaders = shot.attributes?.uploadOperations?.first?.requestHeaders, + let baseURL = URL(string: url) + else { + ascAPI.addMessage("内购商品:\(product.productId) ,创建送审截图失败!苹果参数异常~ ❌ ") + return + } var request = URLRequest(url: baseURL) request.httpMethod = method @@ -364,18 +401,23 @@ extension APUploadIAPListVC { ascAPI.addMessage("上传新的送审截图:\(uploadFileName)") // 上传图片 - guard let response = try? await URLSession.shared.upload(for: request, fromFile: imaUrl) else { + guard let response = try? await URLSession.shared.upload(for: request, fromFile: imaUrl) + else { ascAPI.addMessage("内购商品:\(product.productId) ,创建送审截图失败!上传图片异常~ ❌ ") return } - guard let responseCode = (response.1 as? HTTPURLResponse)?.statusCode, responseCode == 200 else { - ascAPI.addMessage("内购商品:\(product.productId) ,创建送审截图失败!上传图片异常 \(response.1.description)~ ❌ ") + guard let responseCode = (response.1 as? HTTPURLResponse)?.statusCode, responseCode == 200 + else { + ascAPI.addMessage( + "内购商品:\(product.productId) ,创建送审截图失败!上传图片异常 \(response.1.description)~ ❌ ") return } ascAPI.addMessage("提交新的送审截图:\(uploadFileName)") // 确认图片 - if ((await ascAPI.updateInAppPurchasesScreenshot(iapShotId: shot.id, fileMD5: fileMD5)) != nil) { + if (await ascAPI.updateInAppPurchasesScreenshot(iapShotId: shot.id, fileMD5: fileMD5)) + != nil + { ascAPI.addMessage("内购商品:\(product.productId) ,送审截图上传成功!✅ ") } else { ascAPI.addMessage("内购商品:\(product.productId) ,送审截图可能上传失败! ") @@ -396,11 +438,14 @@ extension APUploadIAPListVC { territories.forEach { territory in allTerritories.append([ "type": "territories", - "id": territory.id + "id": territory.id, ]) } // 更新全部国家地区 - if (await ascAPI.updateInAppPurchasesAvailabilityTerritories(iapId: iapId, availableTerritories: allTerritories, availableInNewTerritories: inNew)) != nil { + if (await ascAPI.updateInAppPurchasesAvailabilityTerritories( + iapId: iapId, availableTerritories: allTerritories, + availableInNewTerritories: inNew)) != nil + { ascAPI.addMessage("选择:所有国家(地区)销售,\(newTerritory),更新成功!✅ ") } else { ascAPI.addMessage("选择:所有国家(地区)销售,\(newTerritory),更新失败!❌ ") @@ -416,19 +461,22 @@ extension APUploadIAPListVC { product.territories.territories?.forEach({ territory in territories.append([ "type": "territories", - "id": territory.id + "id": territory.id, ]) }) - let customerTerritory = product.territories.territories?.map({ $0.id }).joined(separator: ",") ?? "无" - if (await ascAPI.updateInAppPurchasesAvailabilityTerritories(iapId: iapId, availableTerritories: territories, availableInNewTerritories: inNew)) != nil { + let customerTerritory = + product.territories.territories?.map({ $0.id }).joined(separator: ",") ?? "无" + if (await ascAPI.updateInAppPurchasesAvailabilityTerritories( + iapId: iapId, availableTerritories: territories, availableInNewTerritories: inNew)) + != nil + { ascAPI.addMessage("内购商品的销售国家/地区:\(customerTerritory) ,更新成功!✅ ") } else { ascAPI.addMessage("内购商品的销售国家/地区:\(customerTerritory) ,更新失败!❌ ") } } - // MARK: - 上传订阅商品 /// 订阅商品创建或更新 @@ -437,8 +485,8 @@ extension APUploadIAPListVC { var currentSubGroup: ASCSubscriptionGroup? // 1、是否有订阅组,没有时要先创建 var subGroups = await ascAPI.fetchSubscriptionGroups(appId: appId) -// if subGroups.isEmpty { -// } + // if subGroups.isEmpty { + // } for subGroup in subGroups { if subGroup.attributes?.referenceName == groupName { @@ -447,19 +495,25 @@ extension APUploadIAPListVC { } // 创建订阅组 - if currentSubGroup == nil, let group = await ascAPI.createSubscriptionGroups(appId: appId, groupName: groupName) { + if currentSubGroup == nil, + let group = await ascAPI.createSubscriptionGroups(appId: appId, groupName: groupName) + { currentSubGroup = group subGroups.append(group) for localization in product.localizations { - let _ = await ascAPI.createSubscriptionGroupLocalizations(iapGroupId: group.id, name: localization.name, locale: localization.locale, customAppName: nil) + let _ = await ascAPI.createSubscriptionGroupLocalizations( + iapGroupId: group.id, name: localization.name, locale: localization.locale, + customAppName: nil) } } //订阅组设置国际化 if let group = currentSubGroup { if let localization = product.groupLocalization { - let _ = await ascAPI.createSubscriptionGroupLocalizations(iapGroupId: group.id, name: localization.name, locale: localization.locale, customAppName: nil) + let _ = await ascAPI.createSubscriptionGroupLocalizations( + iapGroupId: group.id, name: localization.name, locale: localization.locale, + customAppName: nil) } } @@ -470,15 +524,9 @@ extension APUploadIAPListVC { subscriptions.append(contentsOf: subs) } - - // 3、查看是否存在订阅商品,不存在就创建,存在就更新 let subs = subscriptions.filter({ $0.attributes?.productID == product.productId }) if let sub = subs.first { - ///不做更新 - ascAPI.addMessage("订阅商品已经存在:\(product.productId) ,跳过更新信息...") - return; - ascAPI.addMessage("订阅商品已经存在:\(product.productId) ,开始更新信息中...") // 0. 审核备注如果原来有值,而新字段无值,则使用原值 var product = product @@ -495,13 +543,17 @@ extension APUploadIAPListVC { // 2. 商品本地化语言 ascAPI.addMessage("开始更新订阅商品本地化版本:\(product.productId)") let localizations = await ascAPI.fetchSubscriptionLocalizations(iapId: iap.id) -// for localization in product.localizations { + // for localization in product.localizations { if let localization = product.subscriptionLocalization { // 如果已经存在本地化语言,则更新 - if let locale = localizations.filter({ $0.attributes?.locale == localization.locale }).first { + if let locale = localizations.filter({ + $0.attributes?.locale == localization.locale + }).first { // 更新 ascAPI.addMessage("订阅商品已存在本地化版本:\(localization.locale),开始更新信息中...") - if (await ascAPI.updateSubscriptionLocalization(iapLocaleId: locale.id, localization: localization)) != nil { + if (await ascAPI.updateSubscriptionLocalization( + iapLocaleId: locale.id, localization: localization)) != nil + { // 本地化语言更新成功 ascAPI.addMessage("订阅商品本地化版本:\(localization.locale) ,更新语言成功!✅ ") } else { @@ -510,7 +562,8 @@ extension APUploadIAPListVC { } } else { // 创建 - await createSubscriptionLocalization(iapId: iap.id, localization: localization, product: product, ascAPI: ascAPI) + await createSubscriptionLocalization( + iapId: iap.id, localization: localization, product: product, ascAPI: ascAPI) } } @@ -518,14 +571,20 @@ extension APUploadIAPListVC { await createSubscriptionScreenshot(iapId: iap.id, product: product, ascAPI: ascAPI) // 5. 销售国家或地区 - await updateSubscriptionAvailableTerritories(iapId: iap.id, product: product, ascAPI: ascAPI) + await updateSubscriptionAvailableTerritories( + iapId: iap.id, product: product, ascAPI: ascAPI) // 3. 商品价格档位 await updateSubscriptionPricePoint(iapId: iap.id, product: product, ascAPI: ascAPI) + // Apple 后台偶发会在创建/更新后错误显示“元数据缺失”,补一次等价 PATCH 来触发状态刷新。 + await refreshSubscriptionMetadata(iapId: iap.id, product: product, ascAPI: ascAPI) + } else { // 1. 创建新的商品 - guard let iapGroupId = currentSubGroup?.id, let iap = await ascAPI.createSubscription(iapGroupId: iapGroupId, product: product) else { + guard let iapGroupId = currentSubGroup?.id, + let iap = await ascAPI.createSubscription(iapGroupId: iapGroupId, product: product) + else { // 创建失败 ascAPI.addMessage("订阅商品:\(product.productId) ,创建失败!❌ ") return @@ -533,28 +592,30 @@ extension APUploadIAPListVC { // 2. 商品本地化语言 if let localization = product.subscriptionLocalization { - await createSubscriptionLocalization(iapId: iap.id, localization: localization, product: product, ascAPI: ascAPI) + await createSubscriptionLocalization( + iapId: iap.id, localization: localization, product: product, ascAPI: ascAPI) } -// for localization in product.localizations { -// await createSubscriptionLocalization(iapId: iap.id, localization: localization, product: product, ascAPI: ascAPI) -// } - - + // for localization in product.localizations { + // await createSubscriptionLocalization(iapId: iap.id, localization: localization, product: product, ascAPI: ascAPI) + // } // 4. 商品截图 await createSubscriptionScreenshot(iapId: iap.id, product: product, ascAPI: ascAPI) // 5. 销售国家或地区 - await updateSubscriptionAvailableTerritories(iapId: iap.id, product: product, ascAPI: ascAPI) + await updateSubscriptionAvailableTerritories( + iapId: iap.id, product: product, ascAPI: ascAPI) // 3. 商品价格档位 await updateSubscriptionPricePoint(iapId: iap.id, product: product, ascAPI: ascAPI) + + // Apple 后台偶发会在创建/更新后错误显示“元数据缺失”,补一次等价 PATCH 来触发状态刷新。 + await refreshSubscriptionMetadata(iapId: iap.id, product: product, ascAPI: ascAPI) } ascAPI.addMessage("订阅商品:\(product.productId),\(product.name) ,上传完成!\n") } - /// 更新订阅商品的价格档位 func updateSubscriptionPricePoint(iapId: String, product: IAPProduct, ascAPI: APASCAPI) async { guard let schedule = product.priceSchedules else { @@ -565,26 +626,37 @@ extension APUploadIAPListVC { let baseTerritory = schedule.baseTerritory let baseCustomerPrice = schedule.baseCustomerPrice.normalizePrice() - ascAPI.addMessage("开始更新订阅商品价格点,基准国家:\(product.productId),\(baseTerritory),\(baseCustomerPrice) \n") + ascAPI.addMessage( + "开始更新订阅商品价格点,基准国家:\(product.productId),\(baseTerritory),\(baseCustomerPrice) \n") let isPreservePrice = preserveCurrentPriceBtn.state.rawValue == 1 ascAPI.addMessage("保留自动续期订阅者现有定价:\(isPreservePrice ? "是" : "否")") - let points = await ascAPI.fetchSubscriptionPricePoints(iapId: iapId, territory: [baseTerritory]) - if let point = points.filter({ $0.attributes?.customerPrice!.normalizePrice() == baseCustomerPrice }).first { + let points = await ascAPI.fetchSubscriptionPricePoints( + iapId: iapId, territory: [baseTerritory]) + if let point = points.filter({ + $0.attributes?.customerPrice!.normalizePrice() == baseCustomerPrice + }).first { ascAPI.addMessage("开始更新自定价格:") // 自定价格的国家或地区, 基准国家也算是自定价格 var customerPriceSchedules = schedule.manualPrices - customerPriceSchedules.append(IAPPricePoint(territory: baseTerritory, customerPrice: baseCustomerPrice)) + customerPriceSchedules.append( + IAPPricePoint(territory: baseTerritory, customerPrice: baseCustomerPrice)) let manualPricesTerritory: [String] = customerPriceSchedules.map({ $0.territory }) // 设置自定价格 for pricePoint in customerPriceSchedules { let territory = pricePoint.territory let customerPrice = pricePoint.customerPrice.normalizePrice() - let points = await ascAPI.fetchSubscriptionPricePoints(iapId: iapId, territory: [territory]) - if let point = points.filter({ $0.attributes?.customerPrice!.normalizePrice() == customerPrice }).first { - if (await ascAPI.updateSubscriptionPricePoint(iapId: iapId, pricePointId: point.id, preserveCurrentPrice: isPreservePrice)) != nil { + let points = await ascAPI.fetchSubscriptionPricePoints( + iapId: iapId, territory: [territory]) + if let point = points.filter({ + $0.attributes?.customerPrice!.normalizePrice() == customerPrice + }).first { + if (await ascAPI.updateSubscriptionPricePoint( + iapId: iapId, pricePointId: point.id, preserveCurrentPrice: isPreservePrice)) + != nil + { ascAPI.addMessage("自定价格的订阅商品的价格点:\(territory),\(customerPrice) ,更新价格成功!✅ ") } else { ascAPI.addMessage("自定价格的订阅商品的价格点:\(territory),\(customerPrice) ,更新价格失败!❌ ") @@ -596,7 +668,8 @@ extension APUploadIAPListVC { ascAPI.addMessage("开始更新全球均衡价格:") // 剩余的所有的国家地区的订阅价格点,然后一个一个设置。API不支持全部国家一次配置 - let allPoints = await ascAPI.fetchSubscriptionPricePointsEqualizations(pointId: point.id, territory: nil) + let allPoints = await ascAPI.fetchSubscriptionPricePointsEqualizations( + pointId: point.id, territory: nil) for apoint in allPoints { let territory = apoint.relationships?.territory?.data?.id ?? "" // 自定价格的国家跳过 @@ -604,7 +677,10 @@ extension APUploadIAPListVC { continue } let customerPrice = apoint.attributes?.customerPrice ?? "" - if (await ascAPI.updateSubscriptionPricePoint(iapId: iapId, pricePointId: apoint.id, preserveCurrentPrice: isPreservePrice)) != nil { + if (await ascAPI.updateSubscriptionPricePoint( + iapId: iapId, pricePointId: apoint.id, preserveCurrentPrice: isPreservePrice)) + != nil + { // 价格档位配置成功 ascAPI.addMessage("全球均衡价格的订阅商品的价格点:\(territory),\(customerPrice) ,更新价格成功!✅ ") } else { @@ -618,11 +694,14 @@ extension APUploadIAPListVC { } } - /// 更新订阅商品的本地化信息 - func createSubscriptionLocalization(iapId: String, localization: IAPLocalization, product: IAPProduct, ascAPI: APASCAPI) async { + func createSubscriptionLocalization( + iapId: String, localization: IAPLocalization, product: IAPProduct, ascAPI: APASCAPI + ) async { ascAPI.addMessage("开始更新订阅商品本地化版本:\(product.productId),\(localization.locale)") - if (await ascAPI.createSubscriptionLocalization(iapId: iapId, localization: localization)) != nil { + if (await ascAPI.createSubscriptionLocalization(iapId: iapId, localization: localization)) + != nil + { // 本地化语言配置成功 ascAPI.addMessage("订阅商品本地化版本:\(localization.locale) ,更新语言成功!✅ ") } else { @@ -631,6 +710,38 @@ extension APUploadIAPListVC { } } + /// Apple 后台偶发会缓存订阅元数据状态,补一次无实际变更的更新请求来触发重新校验。 + func refreshSubscriptionMetadata(iapId: String, product: IAPProduct, ascAPI: APASCAPI) async { + ascAPI.addMessage("开始刷新订阅商品元数据状态:\(product.productId)") + + guard (await ascAPI.updateSubscription(iapId: iapId, product: product)) != nil else { + ascAPI.addMessage("订阅商品:\(product.productId) ,刷新基础元数据失败!❌ ") + return + } + + guard let localization = product.subscriptionLocalization else { + ascAPI.addMessage("订阅商品:\(product.productId) 无本地化元数据,跳过刷新本地化状态") + return + } + + let localizations = await ascAPI.fetchSubscriptionLocalizations(iapId: iapId) + guard + let locale = localizations.first(where: { $0.attributes?.locale == localization.locale } + ) + else { + ascAPI.addMessage("订阅商品:\(product.productId) ,未找到本地化 \(localization.locale),跳过本地化刷新") + return + } + + if (await ascAPI.updateSubscriptionLocalization( + iapLocaleId: locale.id, localization: localization)) != nil + { + ascAPI.addMessage("订阅商品:\(product.productId) ,元数据状态刷新成功!✅ ") + } else { + ascAPI.addMessage("订阅商品:\(product.productId) ,刷新本地化元数据失败!❌ ") + } + } + /// 更新订阅商品的送审截图 func createSubscriptionScreenshot(iapId: String, product: IAPProduct, ascAPI: APASCAPI) async { ascAPI.addMessage("开始更新订阅商品的送审截图:\(product.productId),\(product.reviewScreenshot)") @@ -664,7 +775,10 @@ extension APUploadIAPListVC { ascAPI.addMessage("订阅商品截图文件错误:\(imgPath) ,文件大小为 0~") return } - guard let shot = await ascAPI.createSubscriptionScreenshot(iapId: iapId, fileName: uploadFileName, fileSize: imaSize) else { + guard + let shot = await ascAPI.createSubscriptionScreenshot( + iapId: iapId, fileName: uploadFileName, fileSize: imaSize) + else { // 创建失败 ascAPI.addMessage("订阅商品:\(product.productId) ,创建送审截图失败!❌ ") return @@ -672,12 +786,13 @@ extension APUploadIAPListVC { // 根据苹果接口返回的上传接口上传 guard let method = shot.attributes?.uploadOperations?.first?.method, - let url = shot.attributes?.uploadOperations?.first?.url, - let requestHeaders = shot.attributes?.uploadOperations?.first?.requestHeaders, - let baseURL = URL(string: url) else { - ascAPI.addMessage("订阅商品:\(product.productId) ,创建送审截图失败!苹果参数异常~ ❌ ") - return - } + let url = shot.attributes?.uploadOperations?.first?.url, + let requestHeaders = shot.attributes?.uploadOperations?.first?.requestHeaders, + let baseURL = URL(string: url) + else { + ascAPI.addMessage("订阅商品:\(product.productId) ,创建送审截图失败!苹果参数异常~ ❌ ") + return + } var request = URLRequest(url: baseURL) request.httpMethod = method @@ -687,18 +802,22 @@ extension APUploadIAPListVC { ascAPI.addMessage("上传新的送审截图:\(uploadFileName)") // 上传图片 - guard let response = try? await URLSession.shared.upload(for: request, fromFile: imaUrl) else { + guard let response = try? await URLSession.shared.upload(for: request, fromFile: imaUrl) + else { ascAPI.addMessage("订阅商品:\(product.productId) ,创建送审截图失败!上传图片异常~ ❌ ") return } - guard let responseCode = (response.1 as? HTTPURLResponse)?.statusCode, responseCode == 200 else { - ascAPI.addMessage("订阅商品:\(product.productId) ,创建送审截图失败!上传图片异常 \(response.1.description)~ ❌ ") + guard let responseCode = (response.1 as? HTTPURLResponse)?.statusCode, responseCode == 200 + else { + ascAPI.addMessage( + "订阅商品:\(product.productId) ,创建送审截图失败!上传图片异常 \(response.1.description)~ ❌ ") return } ascAPI.addMessage("提交新的送审截图:\(uploadFileName)") // 确认图片 - if ((await ascAPI.updateSubscriptionScreenshot(iapShotId: shot.id, fileMD5: fileMD5)) != nil) { + if (await ascAPI.updateSubscriptionScreenshot(iapShotId: shot.id, fileMD5: fileMD5)) != nil + { ascAPI.addMessage("订阅商品:\(product.productId) ,送审截图上传成功!✅ ") } else { ascAPI.addMessage("订阅商品:\(product.productId) ,送审截图可能上传失败! ") @@ -706,7 +825,9 @@ extension APUploadIAPListVC { } /// 销售国家或地区 - func updateSubscriptionAvailableTerritories(iapId: String, product: IAPProduct, ascAPI: APASCAPI) async { + func updateSubscriptionAvailableTerritories( + iapId: String, product: IAPProduct, ascAPI: APASCAPI + ) async { let inAll = product.territories.availableInAllTerritories let inNew = product.territories.availableInNewTerritories let summary = territoryInfo(product: product) @@ -719,11 +840,14 @@ extension APUploadIAPListVC { territories.forEach { territory in allTerritories.append([ "type": "territories", - "id": territory.id + "id": territory.id, ]) } // 更新全部国家地区 - if (await ascAPI.updateSubscriptionAvailabilityTerritories(iapId: iapId, availableTerritories: allTerritories, availableInNewTerritories: inNew)) != nil { + if (await ascAPI.updateSubscriptionAvailabilityTerritories( + iapId: iapId, availableTerritories: allTerritories, + availableInNewTerritories: inNew)) != nil + { ascAPI.addMessage("选择:所有国家(地区)销售,\(newTerritory),更新成功!✅ ") } else { ascAPI.addMessage("选择:所有国家(地区)销售,\(newTerritory),更新失败!❌ ") @@ -739,12 +863,16 @@ extension APUploadIAPListVC { product.territories.territories?.forEach({ territory in territories.append([ "type": "territories", - "id": territory.id + "id": territory.id, ]) }) - let customerTerritory = product.territories.territories?.map({ $0.id }).joined(separator: ",") ?? "无" - if (await ascAPI.updateSubscriptionAvailabilityTerritories(iapId: iapId, availableTerritories: territories, availableInNewTerritories: inNew)) != nil { + let customerTerritory = + product.territories.territories?.map({ $0.id }).joined(separator: ",") ?? "无" + if (await ascAPI.updateSubscriptionAvailabilityTerritories( + iapId: iapId, availableTerritories: territories, availableInNewTerritories: inNew)) + != nil + { ascAPI.addMessage("订阅商品的销售国家/地区:\(customerTerritory) ,更新成功!✅ ") } else { ascAPI.addMessage("订阅商品的销售国家/地区:\(customerTerritory) ,更新失败!❌ ") @@ -758,65 +886,91 @@ extension APUploadIAPListVC { func territoryInfo(product: IAPProduct) -> String { let inAll = product.territories.availableInAllTerritories let inNew = product.territories.availableInNewTerritories - let customerTerritory = product.territories.territories?.map({ $0.id }).joined(separator: ",") ?? "" + let customerTerritory = + product.territories.territories?.map({ $0.id }).joined(separator: ",") ?? "" let off = !inAll && !inNew && (product.territories.territories?.isEmpty ?? true) - let territory = off ? "下架" : (customerTerritory.isEmpty ? (inAll ? "全部" : "当前下架") : customerTerritory) - let stringValue = "在所有国家/地区销售:'\(inAll ? "是" : "否")'\n将来新国家/地区自动提供:'\(inNew ? "是" : "否")'\n指定国家/地区销售:\(territory)" + let territory = + off ? "下架" : (customerTerritory.isEmpty ? (inAll ? "全部" : "当前下架") : customerTerritory) + let stringValue = + "在所有国家/地区销售:'\(inAll ? "是" : "否")'\n将来新国家/地区自动提供:'\(inNew ? "是" : "否")'\n指定国家/地区销售:\(territory)" return stringValue } } - // MARK: - NSTableViewDelegate extension APUploadIAPListVC: NSTableViewDelegate, NSTableViewDataSource { func numberOfRows(in tableView: NSTableView) -> Int { return iaps.count } - func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { + func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) + -> NSView? + { let iap = iaps[row] switch tableColumn?.identifier.enumValue() { case ColumnIdetifier.id.cellValue: - let cell = tableView.makeView(withIdentifier: ColumnIdetifier.id.cellValue, owner: self) as? NSTableCellView - cell?.textField?.stringValue = String(row+1) + let cell = + tableView.makeView(withIdentifier: ColumnIdetifier.id.cellValue, owner: self) + as? NSTableCellView + cell?.textField?.stringValue = String(row + 1) return cell case ColumnIdetifier.productID.cellValue: - let cell = tableView.makeView(withIdentifier: ColumnIdetifier.productID.cellValue, owner: self) as? NSTableCellView + let cell = + tableView.makeView(withIdentifier: ColumnIdetifier.productID.cellValue, owner: self) + as? NSTableCellView cell?.textField?.stringValue = iap.productId return cell case ColumnIdetifier.productName.cellValue: - let cell = tableView.makeView(withIdentifier: ColumnIdetifier.productName.cellValue, owner: self) as? NSTableCellView + let cell = + tableView.makeView( + withIdentifier: ColumnIdetifier.productName.cellValue, owner: self) + as? NSTableCellView cell?.textField?.stringValue = iap.name return cell case ColumnIdetifier.price.cellValue: - let cell = tableView.makeView(withIdentifier: ColumnIdetifier.price.cellValue, owner: self) as? NSTableCellView + let cell = + tableView.makeView(withIdentifier: ColumnIdetifier.price.cellValue, owner: self) + as? NSTableCellView cell?.textField?.stringValue = territoryInfo(product: iap) return cell case ColumnIdetifier.level.cellValue: - let cell = tableView.makeView(withIdentifier: ColumnIdetifier.level.cellValue, owner: self) as? NSTableCellView + let cell = + tableView.makeView(withIdentifier: ColumnIdetifier.level.cellValue, owner: self) + as? NSTableCellView let territory = iap.priceSchedules?.baseTerritory ?? "-" let price = iap.priceSchedules?.baseCustomerPrice ?? "-" - let customerPrice = iap.priceSchedules?.manualPrices.map({ pp in - "{'国家:'\(pp.territory)', '自定价格':'\(pp.customerPrice)'}\n" - }).joined() ?? "-" + let customerPrice = + iap.priceSchedules?.manualPrices.map({ pp in + "{'国家:'\(pp.territory)', '自定价格':'\(pp.customerPrice)'}\n" + }).joined() ?? "-" cell?.textField?.stringValue = "基准国家:'\(territory)'\n基准价格:'\(price)'\n\(customerPrice)" return cell case ColumnIdetifier.productPds.cellValue: - let cell = tableView.makeView(withIdentifier: ColumnIdetifier.productPds.cellValue, owner: self) as? NSTableCellView + let cell = + tableView.makeView( + withIdentifier: ColumnIdetifier.productPds.cellValue, owner: self) + as? NSTableCellView cell?.textField?.stringValue = iap.reviewNote return cell case ColumnIdetifier.state.cellValue: - let cell = tableView.makeView(withIdentifier: ColumnIdetifier.state.cellValue, owner: self) as? NSTableCellView + let cell = + tableView.makeView(withIdentifier: ColumnIdetifier.state.cellValue, owner: self) + as? NSTableCellView cell?.textField?.stringValue = iap.inAppPurchaseType.CNValue() return cell case ColumnIdetifier.screenshot.cellValue: - let cell = tableView.makeView(withIdentifier: ColumnIdetifier.screenshot.cellValue, owner: self) as? ImageViewCell + let cell = + tableView.makeView( + withIdentifier: ColumnIdetifier.screenshot.cellValue, owner: self) + as? ImageViewCell let file_name = iap.reviewScreenshot let imgPath = screenshotPaths[file_name] ?? "" cell?.imgSel.image = NSImage(contentsOfFile: imgPath) return cell case ColumnIdetifier.language.cellValue: - let cell = tableView.makeView(withIdentifier: ColumnIdetifier.language.cellValue, owner: self) as? NSTableCellView + let cell = + tableView.makeView(withIdentifier: ColumnIdetifier.language.cellValue, owner: self) + as? NSTableCellView cell?.textField?.stringValue = iap.localizations.map({ lz in "{'locale:'\(lz.locale)', 'title':'\(lz.name)', 'desc':'\(lz.description)'}\n" }).joined()