解决元数据丢失问题

This commit is contained in:
zeng committed 2026-04-01 11:38:50 +08:00
1 parent 1c33b5b991
commit 809679751b
1 file changed
+273 -119
@@ -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()