解决元数据丢失问题

This commit is contained in:
zeng 2026-04-01 11:38:50 +08:00
parent 1c33b5b991
commit 809679751b

View File

@ -43,9 +43,14 @@ class APUploadIAPListVC: NSViewController {
func showUploadView() { func showUploadView() {
// present // present
DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) { DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) {
let mainStoryboard = NSStoryboard(name: "InAppPurchseView", bundle: Bundle(for: self.classForCoder)) let mainStoryboard = NSStoryboard(
let upVC = mainStoryboard.instantiateController(withIdentifier: "IAPUploadVCID") as? IAPUploadImageVC name: "InAppPurchseView", bundle: Bundle(for: self.classForCoder))
let screenshot = self.iaps.filter({ $0.reviewScreenshot.count > 0 }).map({ $0.reviewScreenshot }) 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 let uniquedshot = screenshot.enumerated().filter { (index, value) -> Bool in
return screenshot.firstIndex(of: value) == index return screenshot.firstIndex(of: value) == index
@ -75,7 +80,6 @@ class APUploadIAPListVC: NSViewController {
return return
} }
guard let appid = currentApp?.appId else { guard let appid = currentApp?.appId else {
APHUD.hide(message: "当前 App 的 appleid 为空!", delayTime: 1) APHUD.hide(message: "当前 App 的 appleid 为空!", delayTime: 1)
return return
@ -109,7 +113,8 @@ extension APUploadIAPListVC {
func updateInAppPurchse(iaps: [IAPProduct], appId: String, ascKey: AppStoreConnectKey) { func updateInAppPurchse(iaps: [IAPProduct], appId: String, ascKey: AppStoreConnectKey) {
let showApiRateLimit = showApiRateLimitLogsBtn.state.rawValue == 1 let showApiRateLimit = showApiRateLimitLogsBtn.state.rawValue == 1
let ascAPI = APASCAPI.init(issuerID: ascKey.issuerID, let ascAPI = APASCAPI.init(
issuerID: ascKey.issuerID,
privateKeyID: ascKey.privateKeyID, privateKeyID: ascKey.privateKeyID,
privateKey: ascKey.privateKey, privateKey: ascKey.privateKey,
showApiRateLimit: showApiRateLimit) showApiRateLimit: showApiRateLimit)
@ -152,7 +157,8 @@ extension APUploadIAPListVC {
if product.inAppPurchaseType == .AUTO_RENEWABLE { if product.inAppPurchaseType == .AUTO_RENEWABLE {
await createRenewSubscription(appId: appId, product: product, ascAPI: ascAPI) await createRenewSubscription(appId: appId, product: product, ascAPI: ascAPI)
} else { } 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: - // 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) ") ascAPI.addMessage("开始上传内购商品:\(product.productId)\(product.name) ")
// //
let iaps = oldIAPs.filter({ $0.attributes?.productID == product.productId }) let iaps = oldIAPs.filter({ $0.attributes?.productID == product.productId })
if let iap = iaps.first { if let iap = iaps.first {
ascAPI.addMessage("订阅商品已经存在:\(product.productId) ,跳过更新信息...") ascAPI.addMessage("订阅商品已经存在:\(product.productId) ,跳过更新信息...")
return; return
ascAPI.addMessage("内购已经存在:\(product.productId) ,开始更新信息中...") ascAPI.addMessage("内购已经存在:\(product.productId) ,开始更新信息中...")
// 0. 使 // 0. 使
@ -180,23 +188,27 @@ extension APUploadIAPListVC {
product.reviewNote = note product.reviewNote = note
} }
// 1. // 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) ,更新信息失败!❌ ") ascAPI.addMessage("内购已经存在:\(product.productId) ,更新信息失败!❌ ")
return return
} }
// 3. // 3.
ascAPI.addMessage("开始更新内购本地化版本:\(product.productId)") ascAPI.addMessage("开始更新内购本地化版本:\(product.productId)")
let localizations = await ascAPI.fetchInAppPurchasesLocalizations(iapId: iap.id) let localizations = await ascAPI.fetchInAppPurchasesLocalizations(iapId: iap.id)
for localization in product.localizations { 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),开始更新信息中...") 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) ,更新语言成功!✅ ") ascAPI.addMessage("内购本地化版本:\(localization.locale) ,更新语言成功!✅ ")
} else { } else {
@ -205,7 +217,8 @@ extension APUploadIAPListVC {
} }
} else { } 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 return
} }
// 3. // 3.
for localization in product.localizations { 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. // 4.
@ -258,23 +271,37 @@ extension APUploadIAPListVC {
ascAPI.addMessage("开始更新价格计划表:\(product.productId)\(baseTerritory)\(baseCustomerPrice) \n") ascAPI.addMessage("开始更新价格计划表:\(product.productId)\(baseTerritory)\(baseCustomerPrice) \n")
let points = await ascAPI.fetchPricePoints(iapId: iapId, territory: [baseTerritory]) 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 manualPrices: [Any] = []
var included: [Any] = [] var included: [Any] = []
ascAPI.addMessage("开始构建基准国家和自定价格:") ascAPI.addMessage("开始构建基准国家和自定价格:")
// base Territory // base Territory
manualPrices.append(["id": "${\(baseTerritory)-\(included.count)}", "type": "inAppPurchasePrices"]) manualPrices.append([
included.append(ascAPI.fetchInAppPurchasePriceSchedule(scheduleId: baseTerritory, pricePointId: point.id, iapId: iapId, index: included.count)) "id": "${\(baseTerritory)-\(included.count)}", "type": "inAppPurchasePrices",
])
included.append(
ascAPI.fetchInAppPurchasePriceSchedule(
scheduleId: baseTerritory, pricePointId: point.id, iapId: iapId,
index: included.count))
// customerPrice // customerPrice
for pricePoint in schedule.manualPrices { for pricePoint in schedule.manualPrices {
let territory = pricePoint.territory let territory = pricePoint.territory
let customerPrice = pricePoint.customerPrice.normalizePrice() let customerPrice = pricePoint.customerPrice.normalizePrice()
let points = await ascAPI.fetchPricePoints(iapId: iapId, territory: [territory]) let points = await ascAPI.fetchPricePoints(iapId: iapId, territory: [territory])
if let point = points.filter({ $0.attributes?.customerPrice!.normalizePrice() == customerPrice }).first { if let point = points.filter({
manualPrices.append(["id": "${\(territory)-\(included.count)}", "type": "inAppPurchasePrices"]) $0.attributes?.customerPrice!.normalizePrice() == customerPrice
included.append(ascAPI.fetchInAppPurchasePriceSchedule(scheduleId: territory, pricePointId: point.id, iapId: iapId, index: included.count)) }).first {
manualPrices.append([
"id": "${\(territory)-\(included.count)}", "type": "inAppPurchasePrices",
])
included.append(
ascAPI.fetchInAppPurchasePriceSchedule(
scheduleId: territory, pricePointId: point.id, iapId: iapId,
index: included.count))
} else { } else {
ascAPI.addMessage("自定价格的内购价格点:\(territory)\(customerPrice) ,未找到此档位!❌ ") ascAPI.addMessage("自定价格的内购价格点:\(territory)\(customerPrice) ,未找到此档位!❌ ")
} }
@ -282,7 +309,10 @@ extension APUploadIAPListVC {
ascAPI.saveLogs(log: "内购的基准国家和自定价格:\(manualPrices)\(included)") 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) ,更新价格成功!✅ ") ascAPI.addMessage("内购价格点:\(baseTerritory)\(baseCustomerPrice) ,更新价格成功!✅ ")
} else { } 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)") 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) ,更新语言成功!✅ ") ascAPI.addMessage("内购本地化版本:\(localization.locale) ,更新语言成功!✅ ")
} else { } else {
@ -341,7 +374,10 @@ extension APUploadIAPListVC {
ascAPI.addMessage("内购商品截图文件错误:\(imgPath) ,文件大小为 0~") ascAPI.addMessage("内购商品截图文件错误:\(imgPath) ,文件大小为 0~")
return 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) ,创建送审截图失败!❌ ") ascAPI.addMessage("内购商品:\(product.productId) ,创建送审截图失败!❌ ")
return return
@ -351,7 +387,8 @@ extension APUploadIAPListVC {
guard let method = shot.attributes?.uploadOperations?.first?.method, guard let method = shot.attributes?.uploadOperations?.first?.method,
let url = shot.attributes?.uploadOperations?.first?.url, let url = shot.attributes?.uploadOperations?.first?.url,
let requestHeaders = shot.attributes?.uploadOperations?.first?.requestHeaders, let requestHeaders = shot.attributes?.uploadOperations?.first?.requestHeaders,
let baseURL = URL(string: url) else { let baseURL = URL(string: url)
else {
ascAPI.addMessage("内购商品:\(product.productId) ,创建送审截图失败!苹果参数异常~ ❌ ") ascAPI.addMessage("内购商品:\(product.productId) ,创建送审截图失败!苹果参数异常~ ❌ ")
return return
} }
@ -364,18 +401,23 @@ extension APUploadIAPListVC {
ascAPI.addMessage("上传新的送审截图:\(uploadFileName)") 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) ,创建送审截图失败!上传图片异常~ ❌ ") ascAPI.addMessage("内购商品:\(product.productId) ,创建送审截图失败!上传图片异常~ ❌ ")
return return
} }
guard let responseCode = (response.1 as? HTTPURLResponse)?.statusCode, responseCode == 200 else { guard let responseCode = (response.1 as? HTTPURLResponse)?.statusCode, responseCode == 200
ascAPI.addMessage("内购商品:\(product.productId) ,创建送审截图失败!上传图片异常 \(response.1.description)~ ❌ ") else {
ascAPI.addMessage(
"内购商品:\(product.productId) ,创建送审截图失败!上传图片异常 \(response.1.description)~ ❌ ")
return return
} }
ascAPI.addMessage("提交新的送审截图:\(uploadFileName)") 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) ,送审截图上传成功!✅ ") ascAPI.addMessage("内购商品:\(product.productId) ,送审截图上传成功!✅ ")
} else { } else {
ascAPI.addMessage("内购商品:\(product.productId) ,送审截图可能上传失败! ") ascAPI.addMessage("内购商品:\(product.productId) ,送审截图可能上传失败! ")
@ -396,11 +438,14 @@ extension APUploadIAPListVC {
territories.forEach { territory in territories.forEach { territory in
allTerritories.append([ allTerritories.append([
"type": "territories", "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),更新成功!✅ ") ascAPI.addMessage("选择:所有国家(地区)销售,\(newTerritory),更新成功!✅ ")
} else { } else {
ascAPI.addMessage("选择:所有国家(地区)销售,\(newTerritory),更新失败!❌ ") ascAPI.addMessage("选择:所有国家(地区)销售,\(newTerritory),更新失败!❌ ")
@ -416,19 +461,22 @@ extension APUploadIAPListVC {
product.territories.territories?.forEach({ territory in product.territories.territories?.forEach({ territory in
territories.append([ territories.append([
"type": "territories", "type": "territories",
"id": territory.id "id": territory.id,
]) ])
}) })
let customerTerritory = product.territories.territories?.map({ $0.id }).joined(separator: ",") ?? "" let customerTerritory =
if (await ascAPI.updateInAppPurchasesAvailabilityTerritories(iapId: iapId, availableTerritories: territories, availableInNewTerritories: inNew)) != nil { product.territories.territories?.map({ $0.id }).joined(separator: ",") ?? ""
if (await ascAPI.updateInAppPurchasesAvailabilityTerritories(
iapId: iapId, availableTerritories: territories, availableInNewTerritories: inNew))
!= nil
{
ascAPI.addMessage("内购商品的销售国家/地区:\(customerTerritory) ,更新成功!✅ ") ascAPI.addMessage("内购商品的销售国家/地区:\(customerTerritory) ,更新成功!✅ ")
} else { } else {
ascAPI.addMessage("内购商品的销售国家/地区:\(customerTerritory) ,更新失败!❌ ") ascAPI.addMessage("内购商品的销售国家/地区:\(customerTerritory) ,更新失败!❌ ")
} }
} }
// MARK: - // MARK: -
/// ///
@ -437,8 +485,8 @@ extension APUploadIAPListVC {
var currentSubGroup: ASCSubscriptionGroup? var currentSubGroup: ASCSubscriptionGroup?
// 1 // 1
var subGroups = await ascAPI.fetchSubscriptionGroups(appId: appId) var subGroups = await ascAPI.fetchSubscriptionGroups(appId: appId)
// if subGroups.isEmpty { // if subGroups.isEmpty {
// } // }
for subGroup in subGroups { for subGroup in subGroups {
if subGroup.attributes?.referenceName == groupName { 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 currentSubGroup = group
subGroups.append(group) subGroups.append(group)
for localization in product.localizations { 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 group = currentSubGroup {
if let localization = product.groupLocalization { 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) subscriptions.append(contentsOf: subs)
} }
// 3 // 3
let subs = subscriptions.filter({ $0.attributes?.productID == product.productId }) let subs = subscriptions.filter({ $0.attributes?.productID == product.productId })
if let sub = subs.first { if let sub = subs.first {
///
ascAPI.addMessage("订阅商品已经存在:\(product.productId) ,跳过更新信息...")
return;
ascAPI.addMessage("订阅商品已经存在:\(product.productId) ,开始更新信息中...") ascAPI.addMessage("订阅商品已经存在:\(product.productId) ,开始更新信息中...")
// 0. 使 // 0. 使
var product = product var product = product
@ -495,13 +543,17 @@ extension APUploadIAPListVC {
// 2. // 2.
ascAPI.addMessage("开始更新订阅商品本地化版本:\(product.productId)") ascAPI.addMessage("开始更新订阅商品本地化版本:\(product.productId)")
let localizations = await ascAPI.fetchSubscriptionLocalizations(iapId: iap.id) 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 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),开始更新信息中...") 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) ,更新语言成功!✅ ") ascAPI.addMessage("订阅商品本地化版本:\(localization.locale) ,更新语言成功!✅ ")
} else { } else {
@ -510,7 +562,8 @@ extension APUploadIAPListVC {
} }
} else { } 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) await createSubscriptionScreenshot(iapId: iap.id, product: product, ascAPI: ascAPI)
// 5. // 5.
await updateSubscriptionAvailableTerritories(iapId: iap.id, product: product, ascAPI: ascAPI) await updateSubscriptionAvailableTerritories(
iapId: iap.id, product: product, ascAPI: ascAPI)
// 3. // 3.
await updateSubscriptionPricePoint(iapId: iap.id, product: product, ascAPI: ascAPI) await updateSubscriptionPricePoint(iapId: iap.id, product: product, ascAPI: ascAPI)
// Apple / PATCH
await refreshSubscriptionMetadata(iapId: iap.id, product: product, ascAPI: ascAPI)
} else { } else {
// 1. // 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) ,创建失败!❌ ") ascAPI.addMessage("订阅商品:\(product.productId) ,创建失败!❌ ")
return return
@ -533,28 +592,30 @@ extension APUploadIAPListVC {
// 2. // 2.
if let localization = product.subscriptionLocalization { 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 { // for localization in product.localizations {
// await createSubscriptionLocalization(iapId: iap.id, localization: localization, product: product, ascAPI: ascAPI) // await createSubscriptionLocalization(iapId: iap.id, localization: localization, product: product, ascAPI: ascAPI)
// } // }
// 4. // 4.
await createSubscriptionScreenshot(iapId: iap.id, product: product, ascAPI: ascAPI) await createSubscriptionScreenshot(iapId: iap.id, product: product, ascAPI: ascAPI)
// 5. // 5.
await updateSubscriptionAvailableTerritories(iapId: iap.id, product: product, ascAPI: ascAPI) await updateSubscriptionAvailableTerritories(
iapId: iap.id, product: product, ascAPI: ascAPI)
// 3. // 3.
await updateSubscriptionPricePoint(iapId: iap.id, product: product, ascAPI: ascAPI) 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") ascAPI.addMessage("订阅商品:\(product.productId)\(product.name) ,上传完成!\n")
} }
/// ///
func updateSubscriptionPricePoint(iapId: String, product: IAPProduct, ascAPI: APASCAPI) async { func updateSubscriptionPricePoint(iapId: String, product: IAPProduct, ascAPI: APASCAPI) async {
guard let schedule = product.priceSchedules else { guard let schedule = product.priceSchedules else {
@ -565,26 +626,37 @@ extension APUploadIAPListVC {
let baseTerritory = schedule.baseTerritory let baseTerritory = schedule.baseTerritory
let baseCustomerPrice = schedule.baseCustomerPrice.normalizePrice() 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 let isPreservePrice = preserveCurrentPriceBtn.state.rawValue == 1
ascAPI.addMessage("保留自动续期订阅者现有定价:\(isPreservePrice ? "" : "")") ascAPI.addMessage("保留自动续期订阅者现有定价:\(isPreservePrice ? "" : "")")
let points = await ascAPI.fetchSubscriptionPricePoints(iapId: iapId, territory: [baseTerritory]) let points = await ascAPI.fetchSubscriptionPricePoints(
if let point = points.filter({ $0.attributes?.customerPrice!.normalizePrice() == baseCustomerPrice }).first { iapId: iapId, territory: [baseTerritory])
if let point = points.filter({
$0.attributes?.customerPrice!.normalizePrice() == baseCustomerPrice
}).first {
ascAPI.addMessage("开始更新自定价格:") ascAPI.addMessage("开始更新自定价格:")
// , // ,
var customerPriceSchedules = schedule.manualPrices 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 }) let manualPricesTerritory: [String] = customerPriceSchedules.map({ $0.territory })
// //
for pricePoint in customerPriceSchedules { for pricePoint in customerPriceSchedules {
let territory = pricePoint.territory let territory = pricePoint.territory
let customerPrice = pricePoint.customerPrice.normalizePrice() let customerPrice = pricePoint.customerPrice.normalizePrice()
let points = await ascAPI.fetchSubscriptionPricePoints(iapId: iapId, territory: [territory]) let points = await ascAPI.fetchSubscriptionPricePoints(
if let point = points.filter({ $0.attributes?.customerPrice!.normalizePrice() == customerPrice }).first { iapId: iapId, territory: [territory])
if (await ascAPI.updateSubscriptionPricePoint(iapId: iapId, pricePointId: point.id, preserveCurrentPrice: isPreservePrice)) != nil { 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) ,更新价格成功!✅ ") ascAPI.addMessage("自定价格的订阅商品的价格点:\(territory)\(customerPrice) ,更新价格成功!✅ ")
} else { } else {
ascAPI.addMessage("自定价格的订阅商品的价格点:\(territory)\(customerPrice) ,更新价格失败!❌ ") ascAPI.addMessage("自定价格的订阅商品的价格点:\(territory)\(customerPrice) ,更新价格失败!❌ ")
@ -596,7 +668,8 @@ extension APUploadIAPListVC {
ascAPI.addMessage("开始更新全球均衡价格:") ascAPI.addMessage("开始更新全球均衡价格:")
// API // 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 { for apoint in allPoints {
let territory = apoint.relationships?.territory?.data?.id ?? "" let territory = apoint.relationships?.territory?.data?.id ?? ""
// //
@ -604,7 +677,10 @@ extension APUploadIAPListVC {
continue continue
} }
let customerPrice = apoint.attributes?.customerPrice ?? "" 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) ,更新价格成功!✅ ") ascAPI.addMessage("全球均衡价格的订阅商品的价格点:\(territory)\(customerPrice) ,更新价格成功!✅ ")
} else { } 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)") 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) ,更新语言成功!✅ ") ascAPI.addMessage("订阅商品本地化版本:\(localization.locale) ,更新语言成功!✅ ")
} else { } 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 { func createSubscriptionScreenshot(iapId: String, product: IAPProduct, ascAPI: APASCAPI) async {
ascAPI.addMessage("开始更新订阅商品的送审截图:\(product.productId)\(product.reviewScreenshot)") ascAPI.addMessage("开始更新订阅商品的送审截图:\(product.productId)\(product.reviewScreenshot)")
@ -664,7 +775,10 @@ extension APUploadIAPListVC {
ascAPI.addMessage("订阅商品截图文件错误:\(imgPath) ,文件大小为 0~") ascAPI.addMessage("订阅商品截图文件错误:\(imgPath) ,文件大小为 0~")
return 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) ,创建送审截图失败!❌ ") ascAPI.addMessage("订阅商品:\(product.productId) ,创建送审截图失败!❌ ")
return return
@ -674,7 +788,8 @@ extension APUploadIAPListVC {
guard let method = shot.attributes?.uploadOperations?.first?.method, guard let method = shot.attributes?.uploadOperations?.first?.method,
let url = shot.attributes?.uploadOperations?.first?.url, let url = shot.attributes?.uploadOperations?.first?.url,
let requestHeaders = shot.attributes?.uploadOperations?.first?.requestHeaders, let requestHeaders = shot.attributes?.uploadOperations?.first?.requestHeaders,
let baseURL = URL(string: url) else { let baseURL = URL(string: url)
else {
ascAPI.addMessage("订阅商品:\(product.productId) ,创建送审截图失败!苹果参数异常~ ❌ ") ascAPI.addMessage("订阅商品:\(product.productId) ,创建送审截图失败!苹果参数异常~ ❌ ")
return return
} }
@ -687,18 +802,22 @@ extension APUploadIAPListVC {
ascAPI.addMessage("上传新的送审截图:\(uploadFileName)") 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) ,创建送审截图失败!上传图片异常~ ❌ ") ascAPI.addMessage("订阅商品:\(product.productId) ,创建送审截图失败!上传图片异常~ ❌ ")
return return
} }
guard let responseCode = (response.1 as? HTTPURLResponse)?.statusCode, responseCode == 200 else { guard let responseCode = (response.1 as? HTTPURLResponse)?.statusCode, responseCode == 200
ascAPI.addMessage("订阅商品:\(product.productId) ,创建送审截图失败!上传图片异常 \(response.1.description)~ ❌ ") else {
ascAPI.addMessage(
"订阅商品:\(product.productId) ,创建送审截图失败!上传图片异常 \(response.1.description)~ ❌ ")
return return
} }
ascAPI.addMessage("提交新的送审截图:\(uploadFileName)") 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) ,送审截图上传成功!✅ ") ascAPI.addMessage("订阅商品:\(product.productId) ,送审截图上传成功!✅ ")
} else { } else {
ascAPI.addMessage("订阅商品:\(product.productId) ,送审截图可能上传失败! ") 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 inAll = product.territories.availableInAllTerritories
let inNew = product.territories.availableInNewTerritories let inNew = product.territories.availableInNewTerritories
let summary = territoryInfo(product: product) let summary = territoryInfo(product: product)
@ -719,11 +840,14 @@ extension APUploadIAPListVC {
territories.forEach { territory in territories.forEach { territory in
allTerritories.append([ allTerritories.append([
"type": "territories", "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),更新成功!✅ ") ascAPI.addMessage("选择:所有国家(地区)销售,\(newTerritory),更新成功!✅ ")
} else { } else {
ascAPI.addMessage("选择:所有国家(地区)销售,\(newTerritory),更新失败!❌ ") ascAPI.addMessage("选择:所有国家(地区)销售,\(newTerritory),更新失败!❌ ")
@ -739,12 +863,16 @@ extension APUploadIAPListVC {
product.territories.territories?.forEach({ territory in product.territories.territories?.forEach({ territory in
territories.append([ territories.append([
"type": "territories", "type": "territories",
"id": territory.id "id": territory.id,
]) ])
}) })
let customerTerritory = product.territories.territories?.map({ $0.id }).joined(separator: ",") ?? "" let customerTerritory =
if (await ascAPI.updateSubscriptionAvailabilityTerritories(iapId: iapId, availableTerritories: territories, availableInNewTerritories: inNew)) != nil { product.territories.territories?.map({ $0.id }).joined(separator: ",") ?? ""
if (await ascAPI.updateSubscriptionAvailabilityTerritories(
iapId: iapId, availableTerritories: territories, availableInNewTerritories: inNew))
!= nil
{
ascAPI.addMessage("订阅商品的销售国家/地区:\(customerTerritory) ,更新成功!✅ ") ascAPI.addMessage("订阅商品的销售国家/地区:\(customerTerritory) ,更新成功!✅ ")
} else { } else {
ascAPI.addMessage("订阅商品的销售国家/地区:\(customerTerritory) ,更新失败!❌ ") ascAPI.addMessage("订阅商品的销售国家/地区:\(customerTerritory) ,更新失败!❌ ")
@ -758,65 +886,91 @@ extension APUploadIAPListVC {
func territoryInfo(product: IAPProduct) -> String { func territoryInfo(product: IAPProduct) -> String {
let inAll = product.territories.availableInAllTerritories let inAll = product.territories.availableInAllTerritories
let inNew = product.territories.availableInNewTerritories 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 off = !inAll && !inNew && (product.territories.territories?.isEmpty ?? true)
let territory = off ? "下架" : (customerTerritory.isEmpty ? (inAll ? "全部" : "当前下架") : customerTerritory) let territory =
let stringValue = "在所有国家/地区销售:'\(inAll ? "" : "")'\n将来新国家/地区自动提供:'\(inNew ? "" : "")'\n指定国家/地区销售:\(territory)" off ? "下架" : (customerTerritory.isEmpty ? (inAll ? "全部" : "当前下架") : customerTerritory)
let stringValue =
"在所有国家/地区销售:'\(inAll ? "" : "")'\n将来新国家/地区自动提供:'\(inNew ? "" : "")'\n指定国家/地区销售:\(territory)"
return stringValue return stringValue
} }
} }
// MARK: - NSTableViewDelegate // MARK: - NSTableViewDelegate
extension APUploadIAPListVC: NSTableViewDelegate, NSTableViewDataSource { extension APUploadIAPListVC: NSTableViewDelegate, NSTableViewDataSource {
func numberOfRows(in tableView: NSTableView) -> Int { func numberOfRows(in tableView: NSTableView) -> Int {
return iaps.count 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] let iap = iaps[row]
switch tableColumn?.identifier.enumValue() { switch tableColumn?.identifier.enumValue() {
case ColumnIdetifier.id.cellValue: case ColumnIdetifier.id.cellValue:
let cell = tableView.makeView(withIdentifier: ColumnIdetifier.id.cellValue, owner: self) as? NSTableCellView let cell =
cell?.textField?.stringValue = String(row+1) tableView.makeView(withIdentifier: ColumnIdetifier.id.cellValue, owner: self)
as? NSTableCellView
cell?.textField?.stringValue = String(row + 1)
return cell return cell
case ColumnIdetifier.productID.cellValue: 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 cell?.textField?.stringValue = iap.productId
return cell return cell
case ColumnIdetifier.productName.cellValue: 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 cell?.textField?.stringValue = iap.name
return cell return cell
case ColumnIdetifier.price.cellValue: 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) cell?.textField?.stringValue = territoryInfo(product: iap)
return cell return cell
case ColumnIdetifier.level.cellValue: 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 territory = iap.priceSchedules?.baseTerritory ?? "-"
let price = iap.priceSchedules?.baseCustomerPrice ?? "-" let price = iap.priceSchedules?.baseCustomerPrice ?? "-"
let customerPrice = iap.priceSchedules?.manualPrices.map({ pp in let customerPrice =
iap.priceSchedules?.manualPrices.map({ pp in
"{'国家:'\(pp.territory)', '自定价格':'\(pp.customerPrice)'}\n" "{'国家:'\(pp.territory)', '自定价格':'\(pp.customerPrice)'}\n"
}).joined() ?? "-" }).joined() ?? "-"
cell?.textField?.stringValue = "基准国家:'\(territory)'\n基准价格:'\(price)'\n\(customerPrice)" cell?.textField?.stringValue = "基准国家:'\(territory)'\n基准价格:'\(price)'\n\(customerPrice)"
return cell return cell
case ColumnIdetifier.productPds.cellValue: 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 cell?.textField?.stringValue = iap.reviewNote
return cell return cell
case ColumnIdetifier.state.cellValue: 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() cell?.textField?.stringValue = iap.inAppPurchaseType.CNValue()
return cell return cell
case ColumnIdetifier.screenshot.cellValue: 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 file_name = iap.reviewScreenshot
let imgPath = screenshotPaths[file_name] ?? "" let imgPath = screenshotPaths[file_name] ?? ""
cell?.imgSel.image = NSImage(contentsOfFile: imgPath) cell?.imgSel.image = NSImage(contentsOfFile: imgPath)
return cell return cell
case ColumnIdetifier.language.cellValue: 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 cell?.textField?.stringValue = iap.localizations.map({ lz in
"{'locale:'\(lz.locale)', 'title':'\(lz.name)', 'desc':'\(lz.description)'}\n" "{'locale:'\(lz.locale)', 'title':'\(lz.name)', 'desc':'\(lz.description)'}\n"
}).joined() }).joined()