修复上传图片失败问题

This commit is contained in:
zeng 2026-01-27 15:16:20 +08:00
parent 261c75c8b7
commit 54aeb47fd9
2 changed files with 217 additions and 196 deletions

View File

@ -8,12 +8,12 @@
import Cocoa import Cocoa
class APUploadIAPListVC: NSViewController { class APUploadIAPListVC: NSViewController {
@IBOutlet weak var tableView: NSTableView! @IBOutlet weak var tableView: NSTableView!
@IBOutlet weak var enterBtn: NSButton! @IBOutlet weak var enterBtn: NSButton!
@IBOutlet weak var preserveCurrentPriceBtn: NSButton! @IBOutlet weak var preserveCurrentPriceBtn: NSButton!
@IBOutlet weak var showApiRateLimitLogsBtn: NSButton! @IBOutlet weak var showApiRateLimitLogsBtn: NSButton!
public var currentApp: App? { public var currentApp: App? {
didSet { didSet {
setupUI() setupUI()
@ -24,22 +24,22 @@ class APUploadIAPListVC: NSViewController {
self.tableView.reloadData() self.tableView.reloadData()
} }
} }
private var screenshotPaths = [String: String]() private var screenshotPaths = [String: String]()
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
self.tableView.columnAutoresizingStyle = .uniformColumnAutoresizingStyle self.tableView.columnAutoresizingStyle = .uniformColumnAutoresizingStyle
self.tableView.selectionHighlightStyle = .none self.tableView.selectionHighlightStyle = .none
self.tableView.sizeToFit() self.tableView.sizeToFit()
} }
func setupUI() { func setupUI() {
self.view.window?.title = "批量内购买项目上传 - " + (currentApp?.appName ?? "") self.view.window?.title = "批量内购买项目上传 - " + (currentApp?.appName ?? "")
self.tableView.reloadData() self.tableView.reloadData()
} }
func showUploadView() { func showUploadView() {
// present // present
DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) { DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) {
@ -58,36 +58,36 @@ class APUploadIAPListVC: NSViewController {
self.presentAsSheet(upVC!) self.presentAsSheet(upVC!)
} }
} }
@IBAction func clickedUploadShotBtn(_ sender: Any) { @IBAction func clickedUploadShotBtn(_ sender: Any) {
showUploadView() showUploadView()
} }
@IBAction func clickedSPasswordBtn(_ sender: Any) { @IBAction func clickedSPasswordBtn(_ sender: Any) {
let vc = APASCKeysSettingVC() let vc = APASCKeysSettingVC()
presentAsSheet(vc) presentAsSheet(vc)
} }
@IBAction func createIAP(_ sender: Any) { @IBAction func createIAP(_ sender: Any) {
let list = self.iaps let list = self.iaps
guard list.count > 0 else { guard list.count > 0 else {
APHUD.hide(message: "当前 App 无上传的内购商品!", delayTime: 1) APHUD.hide(message: "当前 App 无上传的内购商品!", delayTime: 1)
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
} }
enterBtn.isEnabled = false enterBtn.isEnabled = false
APHUD.show(message: "上传中", view: self.view) APHUD.show(message: "上传中", view: self.view)
let uploadIAPs: ((AppStoreConnectKey) -> Void) = { [weak self] ascKey in let uploadIAPs: ((AppStoreConnectKey) -> Void) = { [weak self] ascKey in
// //
self?.updateInAppPurchse(iaps: list, appId: appid, ascKey: ascKey) self?.updateInAppPurchse(iaps: list, appId: appid, ascKey: ascKey)
} }
guard let ascKey = InfoCenter.shared.currentASCKey else { guard let ascKey = InfoCenter.shared.currentASCKey else {
let vc = APASCKeysSettingVC() let vc = APASCKeysSettingVC()
vc.updateCompletion = { password in vc.updateCompletion = { password in
@ -98,7 +98,7 @@ class APUploadIAPListVC: NSViewController {
presentAsSheet(vc) presentAsSheet(vc)
return return
} }
uploadIAPs(ascKey) uploadIAPs(ascKey)
} }
@ -106,7 +106,7 @@ class APUploadIAPListVC: NSViewController {
// MARK: - // MARK: -
extension APUploadIAPListVC { 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,
@ -114,7 +114,7 @@ extension APUploadIAPListVC {
privateKey: ascKey.privateKey, privateKey: ascKey.privateKey,
showApiRateLimit: showApiRateLimit) showApiRateLimit: showApiRateLimit)
ascAPI.addMessage("密钥信息:\(ascKey.issuerID), \(ascKey.privateKeyID), \(ascKey.privateKey)") ascAPI.addMessage("密钥信息:\(ascKey.issuerID), \(ascKey.privateKeyID), \(ascKey.privateKey)")
Task { Task {
// 1 app app // 1 app app
guard let apps = await ascAPI.apps() else { guard let apps = await ascAPI.apps() else {
@ -130,7 +130,7 @@ extension APUploadIAPListVC {
APHUD.hide(message: "当前的密钥没有查到App: \(appId),请检查~", delayTime: 2) APHUD.hide(message: "当前的密钥没有查到App: \(appId),请检查~", delayTime: 2)
return return
} }
// 2. // 2.
let sb = NSStoryboard(name: "APDebugVC", bundle: Bundle(for: self.classForCoder)) let sb = NSStoryboard(name: "APDebugVC", bundle: Bundle(for: self.classForCoder))
let newWC = sb.instantiateController(withIdentifier: "APDebugWC") as? NSWindowController let newWC = sb.instantiateController(withIdentifier: "APDebugWC") as? NSWindowController
@ -141,11 +141,11 @@ extension APUploadIAPListVC {
ascAPI.updateMsg = { messages in ascAPI.updateMsg = { messages in
logVC?.debugLog = messages.joined(separator: "\n") logVC?.debugLog = messages.joined(separator: "\n")
} }
ascAPI.addMessage("开始处理内购商品,获取现有商品中...") ascAPI.addMessage("开始处理内购商品,获取现有商品中...")
// 3 // 3
let oldIAPs = await ascAPI.fetchInAppPurchasesList(appId: appId) let oldIAPs = await ascAPI.fetchInAppPurchasesList(appId: appId)
// 4 // 4
for product in iaps { for product in iaps {
// //
@ -155,15 +155,15 @@ extension APUploadIAPListVC {
await createInAppPurchase(appId: appId, product: product, oldIAPs: oldIAPs, ascAPI: ascAPI) await createInAppPurchase(appId: appId, product: product, oldIAPs: oldIAPs, ascAPI: ascAPI)
} }
} }
self.enterBtn.isEnabled = true self.enterBtn.isEnabled = true
APHUD.hide() APHUD.hide()
ascAPI.addMessage("完成全部内购商品,可稍后在苹果后台查看!✅✅✅") ascAPI.addMessage("完成全部内购商品,可稍后在苹果后台查看!✅✅✅")
} }
} }
// 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) ")
@ -172,7 +172,7 @@ extension APUploadIAPListVC {
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. 使
var product = product var product = product
@ -185,8 +185,8 @@ extension APUploadIAPListVC {
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)
@ -208,16 +208,16 @@ extension APUploadIAPListVC {
await createIAPLocalization(iapId: iap.id, localization: localization, product: product, ascAPI: ascAPI) await createIAPLocalization(iapId: iap.id, localization: localization, product: product, ascAPI: ascAPI)
} }
} }
// 4. // 4.
await createIAPScreenshot(iapId: iap.id, product: product, ascAPI: ascAPI) await createIAPScreenshot(iapId: iap.id, product: product, ascAPI: ascAPI)
// 5. // 5.
await updateIAPAvailableTerritories(iapId: iap.id, product: product, ascAPI: ascAPI) await updateIAPAvailableTerritories(iapId: iap.id, product: product, ascAPI: ascAPI)
// 2. // 2.
await updateIAPPricePoint(iapId: iap.id, product: product, ascAPI: ascAPI) await updateIAPPricePoint(iapId: iap.id, product: product, ascAPI: ascAPI)
} else { } else {
// 1. // 1.
guard let iap = await ascAPI.createInAppPurchases(appId: appId, product: product) else { guard let iap = await ascAPI.createInAppPurchases(appId: appId, product: product) else {
@ -225,48 +225,48 @@ extension APUploadIAPListVC {
ascAPI.addMessage("内购商品:\(product.productId) ,创建失败!❌ ") ascAPI.addMessage("内购商品:\(product.productId) ,创建失败!❌ ")
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.
await createIAPScreenshot(iapId: iap.id, product: product, ascAPI: ascAPI) await createIAPScreenshot(iapId: iap.id, product: product, ascAPI: ascAPI)
// 5. // 5.
await updateIAPAvailableTerritories(iapId: iap.id, product: product, ascAPI: ascAPI) await updateIAPAvailableTerritories(iapId: iap.id, product: product, ascAPI: ascAPI)
// 2. // 2.
await updateIAPPricePoint(iapId: iap.id, product: product, ascAPI: ascAPI) await updateIAPPricePoint(iapId: iap.id, product: product, ascAPI: ascAPI)
} }
ascAPI.addMessage("内购商品:\(product.productId)\(product.name) ,上传完成!\n") ascAPI.addMessage("内购商品:\(product.productId)\(product.name) ,上传完成!\n")
} }
/// ///
func updateIAPPricePoint(iapId: String, product: IAPProduct, ascAPI: APASCAPI) async { func updateIAPPricePoint(iapId: String, product: IAPProduct, ascAPI: APASCAPI) async {
guard let schedule = product.priceSchedules else { guard let schedule = product.priceSchedules else {
ascAPI.addMessage("无价格计划表:\(product.productId) ,请确认!❌ ") ascAPI.addMessage("无价格计划表:\(product.productId) ,请确认!❌ ")
return return
} }
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 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(["id": "${\(baseTerritory)-\(included.count)}", "type": "inAppPurchasePrices"])
included.append(ascAPI.fetchInAppPurchasePriceSchedule(scheduleId: baseTerritory, pricePointId: point.id, iapId: iapId, index: included.count)) 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
@ -279,9 +279,9 @@ extension APUploadIAPListVC {
ascAPI.addMessage("自定价格的内购价格点:\(territory)\(customerPrice) ,未找到此档位!❌ ") ascAPI.addMessage("自定价格的内购价格点:\(territory)\(customerPrice) ,未找到此档位!❌ ")
} }
} }
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) ,更新价格成功!✅ ")
@ -294,8 +294,8 @@ extension APUploadIAPListVC {
ascAPI.addMessage("基准国家的内购价格点:\(baseTerritory)\(baseCustomerPrice) ,未找到此档位!❌ ") ascAPI.addMessage("基准国家的内购价格点:\(baseTerritory)\(baseCustomerPrice) ,未找到此档位!❌ ")
} }
} }
/// ///
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)")
@ -307,7 +307,7 @@ extension APUploadIAPListVC {
ascAPI.addMessage("内购本地化版本:\(localization.locale) ,更新语言失败!❌ ") ascAPI.addMessage("内购本地化版本:\(localization.locale) ,更新语言失败!❌ ")
} }
} }
/// ///
func createIAPScreenshot(iapId: String, product: IAPProduct, ascAPI: APASCAPI) async { func createIAPScreenshot(iapId: String, product: IAPProduct, ascAPI: APASCAPI) async {
ascAPI.addMessage("开始更新内购商品的送审截图:\(product.productId)\(product.reviewScreenshot)") ascAPI.addMessage("开始更新内购商品的送审截图:\(product.productId)\(product.reviewScreenshot)")
@ -316,8 +316,9 @@ extension APUploadIAPListVC {
ascAPI.addMessage("内购商品:\(product.productId) 无送审截图或未上传截图~") ascAPI.addMessage("内购商品:\(product.productId) 无送审截图或未上传截图~")
return return
} }
let imaUrl = URL.init(fileURLWithPath: imgPath) let imaUrl = URL.init(fileURLWithPath: imgPath)
let uploadFileName = imaUrl.lastPathComponent.isEmpty ? imgName : imaUrl.lastPathComponent
guard let fileMD5 = URL.init(fileURLWithPath: imgPath).fileMD5() else { guard let fileMD5 = URL.init(fileURLWithPath: imgPath).fileMD5() else {
ascAPI.addMessage("内购商品截图文件错误:\(imgPath) ,无法生成 md5 值~") ascAPI.addMessage("内购商品截图文件错误:\(imgPath) ,无法生成 md5 值~")
return return
@ -332,16 +333,20 @@ extension APUploadIAPListVC {
ascAPI.addMessage("内购商品截图创建失败:\(imgName) ,无法删除旧截图~") ascAPI.addMessage("内购商品截图创建失败:\(imgName) ,无法删除旧截图~")
} }
} }
ascAPI.addMessage("创建新的送审截图:\(product.reviewScreenshot)") ascAPI.addMessage("创建新的送审截图:\(uploadFileName)")
// //
let imaSize = imaUrl.fileSizeInt() let imaSize = imaUrl.fileSizeInt()
guard let shot = await ascAPI.createInAppPurchasesScreenshot(iapId: iapId, fileName: imgName, fileSize: imaSize) else { guard imaSize > 0 else {
ascAPI.addMessage("内购商品截图文件错误:\(imgPath) ,文件大小为 0~")
return
}
guard let shot = await ascAPI.createInAppPurchasesScreenshot(iapId: iapId, fileName: uploadFileName, fileSize: imaSize) else {
// //
ascAPI.addMessage("内购商品:\(product.productId) ,创建送审截图失败!❌ ") ascAPI.addMessage("内购商品:\(product.productId) ,创建送审截图失败!❌ ")
return return
} }
// //
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,
@ -350,14 +355,14 @@ extension APUploadIAPListVC {
ascAPI.addMessage("内购商品:\(product.productId) ,创建送审截图失败!苹果参数异常~ ❌ ") ascAPI.addMessage("内购商品:\(product.productId) ,创建送审截图失败!苹果参数异常~ ❌ ")
return return
} }
var request = URLRequest(url: baseURL) var request = URLRequest(url: baseURL)
request.httpMethod = method request.httpMethod = method
for header in requestHeaders { for header in requestHeaders {
request.headers[header.name ?? ""] = header.value ?? "" request.headers[header.name ?? ""] = header.value ?? ""
} }
ascAPI.addMessage("上传新的送审截图:\(product.reviewScreenshot)") 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) ,创建送审截图失败!上传图片异常~ ❌ ")
@ -367,8 +372,8 @@ extension APUploadIAPListVC {
ascAPI.addMessage("内购商品:\(product.productId) ,创建送审截图失败!上传图片异常 \(response.1.description)~ ❌ ") ascAPI.addMessage("内购商品:\(product.productId) ,创建送审截图失败!上传图片异常 \(response.1.description)~ ❌ ")
return return
} }
ascAPI.addMessage("提交新的送审截图:\(product.reviewScreenshot)") 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) ,送审截图上传成功!✅ ")
@ -376,7 +381,7 @@ extension APUploadIAPListVC {
ascAPI.addMessage("内购商品:\(product.productId) ,送审截图可能上传失败! ") ascAPI.addMessage("内购商品:\(product.productId) ,送审截图可能上传失败! ")
} }
} }
/// ///
func updateIAPAvailableTerritories(iapId: String, product: IAPProduct, ascAPI: APASCAPI) async { func updateIAPAvailableTerritories(iapId: String, product: IAPProduct, ascAPI: APASCAPI) async {
let inAll = product.territories.availableInAllTerritories let inAll = product.territories.availableInAllTerritories
@ -384,7 +389,7 @@ extension APUploadIAPListVC {
let summary = territoryInfo(product: product) let summary = territoryInfo(product: product)
let newTerritory = inNew ? "将来新国家(地区)时自动提供!" : "将来新国家(地区)时不自动提供!" let newTerritory = inNew ? "将来新国家(地区)时自动提供!" : "将来新国家(地区)时不自动提供!"
ascAPI.addMessage("开始更新内购商品的销售国家/地区:\(summary)") ascAPI.addMessage("开始更新内购商品的销售国家/地区:\(summary)")
guard !inAll else { guard !inAll else {
var allTerritories: [[String: String]] = [] var allTerritories: [[String: String]] = []
if let territories = await ascAPI.territories() { if let territories = await ascAPI.territories() {
@ -405,7 +410,7 @@ extension APUploadIAPListVC {
} }
return return
} }
/// ///
var territories: [[String: String]] = [] var territories: [[String: String]] = []
product.territories.territories?.forEach({ territory in product.territories.territories?.forEach({ territory in
@ -414,7 +419,7 @@ extension APUploadIAPListVC {
"id": territory.id "id": territory.id
]) ])
}) })
let customerTerritory = product.territories.territories?.map({ $0.id }).joined(separator: ",") ?? "" let customerTerritory = product.territories.territories?.map({ $0.id }).joined(separator: ",") ?? ""
if (await ascAPI.updateInAppPurchasesAvailabilityTerritories(iapId: iapId, availableTerritories: territories, availableInNewTerritories: inNew)) != nil { if (await ascAPI.updateInAppPurchasesAvailabilityTerritories(iapId: iapId, availableTerritories: territories, availableInNewTerritories: inNew)) != nil {
ascAPI.addMessage("内购商品的销售国家/地区:\(customerTerritory) ,更新成功!✅ ") ascAPI.addMessage("内购商品的销售国家/地区:\(customerTerritory) ,更新成功!✅ ")
@ -422,10 +427,10 @@ extension APUploadIAPListVC {
ascAPI.addMessage("内购商品的销售国家/地区:\(customerTerritory) ,更新失败!❌ ") ascAPI.addMessage("内购商品的销售国家/地区:\(customerTerritory) ,更新失败!❌ ")
} }
} }
// MARK: - // MARK: -
/// ///
func createRenewSubscription(appId: String, product: IAPProduct, ascAPI: APASCAPI) async { func createRenewSubscription(appId: String, product: IAPProduct, ascAPI: APASCAPI) async {
let groupName = product.groupName let groupName = product.groupName
@ -434,46 +439,46 @@ extension APUploadIAPListVC {
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 {
currentSubGroup = subGroup currentSubGroup = subGroup
} }
} }
// //
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)
} }
} }
// 2 // 2
var subscriptions = [ASCSubscription]() var subscriptions = [ASCSubscription]()
for subGroup in subGroups { for subGroup in subGroups {
let subs = await ascAPI.fetchSubscriptionGroupSubscriptions(iapGroupId: subGroup.id) let subs = await ascAPI.fetchSubscriptionGroupSubscriptions(iapGroupId: subGroup.id)
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) ,跳过更新信息...") ascAPI.addMessage("订阅商品已经存在:\(product.productId) ,跳过更新信息...")
return; return;
ascAPI.addMessage("订阅商品已经存在:\(product.productId) ,开始更新信息中...") ascAPI.addMessage("订阅商品已经存在:\(product.productId) ,开始更新信息中...")
// 0. 使 // 0. 使
var product = product var product = product
@ -486,7 +491,7 @@ extension APUploadIAPListVC {
ascAPI.addMessage("订阅商品已经存在:\(product.productId) ,更新信息失败!❌ ") ascAPI.addMessage("订阅商品已经存在:\(product.productId) ,更新信息失败!❌ ")
return return
} }
// 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)
@ -508,16 +513,16 @@ extension APUploadIAPListVC {
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)
} 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 {
@ -525,7 +530,7 @@ extension APUploadIAPListVC {
ascAPI.addMessage("订阅商品:\(product.productId) ,创建失败!❌ ") ascAPI.addMessage("订阅商品:\(product.productId) ,创建失败!❌ ")
return return
} }
// 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)
@ -533,41 +538,41 @@ extension APUploadIAPListVC {
// 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)
} }
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 {
ascAPI.addMessage("无价格计划表:\(product.productId) ,请确认!❌ ") ascAPI.addMessage("无价格计划表:\(product.productId) ,请确认!❌ ")
return return
} }
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(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 {
ascAPI.addMessage("开始更新自定价格:") ascAPI.addMessage("开始更新自定价格:")
// , // ,
var customerPriceSchedules = schedule.manualPrices var customerPriceSchedules = schedule.manualPrices
@ -588,7 +593,7 @@ extension APUploadIAPListVC {
ascAPI.addMessage("自定价格的订阅商品价格点:\(territory)\(customerPrice) ,未找到此档位!❌ ") ascAPI.addMessage("自定价格的订阅商品价格点:\(territory)\(customerPrice) ,未找到此档位!❌ ")
} }
} }
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)
@ -612,8 +617,8 @@ extension APUploadIAPListVC {
ascAPI.addMessage("基准国家的订阅商品价格点:\(baseTerritory)\(baseCustomerPrice) ,未找到此档位!❌ ") ascAPI.addMessage("基准国家的订阅商品价格点:\(baseTerritory)\(baseCustomerPrice) ,未找到此档位!❌ ")
} }
} }
/// ///
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)")
@ -625,7 +630,7 @@ extension APUploadIAPListVC {
ascAPI.addMessage("订阅商品本地化版本:\(localization.locale) ,更新语言失败!❌ ") ascAPI.addMessage("订阅商品本地化版本:\(localization.locale) ,更新语言失败!❌ ")
} }
} }
/// ///
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)")
@ -634,8 +639,9 @@ extension APUploadIAPListVC {
ascAPI.addMessage("订阅商品:\(product.productId) 无送审截图或未上传截图~") ascAPI.addMessage("订阅商品:\(product.productId) 无送审截图或未上传截图~")
return return
} }
let imaUrl = URL.init(fileURLWithPath: imgPath) let imaUrl = URL.init(fileURLWithPath: imgPath)
let uploadFileName = imaUrl.lastPathComponent.isEmpty ? imgName : imaUrl.lastPathComponent
guard let fileMD5 = URL.init(fileURLWithPath: imgPath).fileMD5() else { guard let fileMD5 = URL.init(fileURLWithPath: imgPath).fileMD5() else {
ascAPI.addMessage("订阅商品截图文件错误:\(imgPath) ,无法生成 md5 值~") ascAPI.addMessage("订阅商品截图文件错误:\(imgPath) ,无法生成 md5 值~")
return return
@ -650,16 +656,20 @@ extension APUploadIAPListVC {
ascAPI.addMessage("订阅商品截图创建失败:\(imgName) ,无法删除旧截图~") ascAPI.addMessage("订阅商品截图创建失败:\(imgName) ,无法删除旧截图~")
} }
} }
ascAPI.addMessage("创建新的送审截图:\(product.reviewScreenshot)") ascAPI.addMessage("创建新的送审截图:\(uploadFileName)")
// //
let imaSize = imaUrl.fileSizeInt() let imaSize = imaUrl.fileSizeInt()
guard let shot = await ascAPI.createSubscriptionScreenshot(iapId: iapId, fileName: imgName, fileSize: imaSize) else { guard imaSize > 0 else {
ascAPI.addMessage("订阅商品截图文件错误:\(imgPath) ,文件大小为 0~")
return
}
guard let shot = await ascAPI.createSubscriptionScreenshot(iapId: iapId, fileName: uploadFileName, fileSize: imaSize) else {
// //
ascAPI.addMessage("订阅商品:\(product.productId) ,创建送审截图失败!❌ ") ascAPI.addMessage("订阅商品:\(product.productId) ,创建送审截图失败!❌ ")
return return
} }
// //
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,
@ -668,14 +678,14 @@ extension APUploadIAPListVC {
ascAPI.addMessage("订阅商品:\(product.productId) ,创建送审截图失败!苹果参数异常~ ❌ ") ascAPI.addMessage("订阅商品:\(product.productId) ,创建送审截图失败!苹果参数异常~ ❌ ")
return return
} }
var request = URLRequest(url: baseURL) var request = URLRequest(url: baseURL)
request.httpMethod = method request.httpMethod = method
for header in requestHeaders { for header in requestHeaders {
request.headers[header.name ?? ""] = header.value ?? "" request.headers[header.name ?? ""] = header.value ?? ""
} }
ascAPI.addMessage("上传新的送审截图:\(product.reviewScreenshot)") 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) ,创建送审截图失败!上传图片异常~ ❌ ")
@ -685,8 +695,8 @@ extension APUploadIAPListVC {
ascAPI.addMessage("订阅商品:\(product.productId) ,创建送审截图失败!上传图片异常 \(response.1.description)~ ❌ ") ascAPI.addMessage("订阅商品:\(product.productId) ,创建送审截图失败!上传图片异常 \(response.1.description)~ ❌ ")
return return
} }
ascAPI.addMessage("提交新的送审截图:\(product.reviewScreenshot)") 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) ,送审截图上传成功!✅ ")
@ -694,7 +704,7 @@ extension APUploadIAPListVC {
ascAPI.addMessage("订阅商品:\(product.productId) ,送审截图可能上传失败! ") ascAPI.addMessage("订阅商品:\(product.productId) ,送审截图可能上传失败! ")
} }
} }
/// ///
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
@ -702,7 +712,7 @@ extension APUploadIAPListVC {
let summary = territoryInfo(product: product) let summary = territoryInfo(product: product)
let newTerritory = inNew ? "将来新国家(地区)时自动提供!" : "将来新国家(地区)时不自动提供!" let newTerritory = inNew ? "将来新国家(地区)时自动提供!" : "将来新国家(地区)时不自动提供!"
ascAPI.addMessage("开始更新订阅商品的销售国家/地区:\(summary)") ascAPI.addMessage("开始更新订阅商品的销售国家/地区:\(summary)")
guard !inAll else { guard !inAll else {
var allTerritories: [[String: String]] = [] var allTerritories: [[String: String]] = []
if let territories = await ascAPI.territories() { if let territories = await ascAPI.territories() {
@ -723,7 +733,7 @@ extension APUploadIAPListVC {
} }
return return
} }
/// ///
var territories: [[String: String]] = [] var territories: [[String: String]] = []
product.territories.territories?.forEach({ territory in product.territories.territories?.forEach({ territory in
@ -732,7 +742,7 @@ extension APUploadIAPListVC {
"id": territory.id "id": territory.id
]) ])
}) })
let customerTerritory = product.territories.territories?.map({ $0.id }).joined(separator: ",") ?? "" let customerTerritory = product.territories.territories?.map({ $0.id }).joined(separator: ",") ?? ""
if (await ascAPI.updateSubscriptionAvailabilityTerritories(iapId: iapId, availableTerritories: territories, availableInNewTerritories: inNew)) != nil { if (await ascAPI.updateSubscriptionAvailabilityTerritories(iapId: iapId, availableTerritories: territories, availableInNewTerritories: inNew)) != nil {
ascAPI.addMessage("订阅商品的销售国家/地区:\(customerTerritory) ,更新成功!✅ ") ascAPI.addMessage("订阅商品的销售国家/地区:\(customerTerritory) ,更新成功!✅ ")
@ -744,7 +754,7 @@ extension APUploadIAPListVC {
// MARK: - Privacy Method // MARK: - Privacy Method
extension APUploadIAPListVC { 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
@ -762,7 +772,7 @@ 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() {
@ -815,7 +825,7 @@ extension APUploadIAPListVC: NSTableViewDelegate, NSTableViewDataSource {
return nil return nil
} }
} }
func tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat { func tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat {
let prices = iaps[row].priceSchedules?.manualPrices.count ?? 0 let prices = iaps[row].priceSchedules?.manualPrices.count ?? 0
let count = max(prices + 2, 3) let count = max(prices + 2, 3)

View File

@ -34,14 +34,14 @@ typealias ASCSubscriptionAvailability = AppStoreConnect_Swift_SDK.SubscriptionAv
class APASCAPI { class APASCAPI {
public var updateMsg: (([String])->Void)? public var updateMsg: (([String])->Void)?
private var message = [String]() { private var message = [String]() {
didSet { didSet {
updateMsg?(message) updateMsg?(message)
} }
} }
// //
private let date = Date() private let date = Date()
var logPath: String { var logPath: String {
@ -52,14 +52,14 @@ class APASCAPI {
return "/AppleParty/ASCAPI/log_\(currentDate).txt" return "/AppleParty/ASCAPI/log_\(currentDate).txt"
} }
} }
/// Go to https://appstoreconnect.apple.com/access/api and create your own key. This is also the page to find the private key ID and the issuer ID. /// Go to https://appstoreconnect.apple.com/access/api and create your own key. This is also the page to find the private key ID and the issuer ID.
/// Download the private key and open it in a text editor. Remove the enters and copy the contents over to the private key parameter. /// Download the private key and open it in a text editor. Remove the enters and copy the contents over to the private key parameter.
//private let configuration = APIConfiguration(issuerID: "<YOUR ISSUER ID>", privateKeyID: "<YOUR PRIVATE KEY ID>", privateKey: "<YOUR PRIVATE KEY>") //private let configuration = APIConfiguration(issuerID: "<YOUR ISSUER ID>", privateKeyID: "<YOUR PRIVATE KEY ID>", privateKey: "<YOUR PRIVATE KEY>")
//private lazy var provider: APIProvider = APIProvider(configuration: configuration) //private lazy var provider: APIProvider = APIProvider(configuration: configuration)
private var provider: APIProvider? private var provider: APIProvider?
private var rateLimitPublisher: AnyCancellable? private var rateLimitPublisher: AnyCancellable?
init(issuerID: String, privateKeyID: String, privateKey: String, showApiRateLimit: Bool) { init(issuerID: String, privateKeyID: String, privateKey: String, showApiRateLimit: Bool) {
do { do {
let configuration = try APIConfiguration(issuerID: issuerID, privateKeyID: privateKeyID, privateKey: privateKey) let configuration = try APIConfiguration(issuerID: issuerID, privateKeyID: privateKeyID, privateKey: privateKey)
@ -73,7 +73,7 @@ class APASCAPI {
handleError("初始化失败: \(error.localizedDescription)") handleError("初始化失败: \(error.localizedDescription)")
} }
} }
/// app /// app
/// - Returns: apps /// - Returns: apps
func apps() async -> [ASCApp]? { func apps() async -> [ASCApp]? {
@ -97,7 +97,7 @@ class APASCAPI {
return nil return nil
} }
} }
/// App Store /// App Store
/// - Returns: apps /// - Returns: apps
func territories() async -> [ASCTerritory]? { func territories() async -> [ASCTerritory]? {
@ -117,9 +117,9 @@ class APASCAPI {
return nil return nil
} }
} }
// MARK: - API // MARK: - API
/// app /// app
/// - Parameter appId: apple id /// - Parameter appId: apple id
/// - Returns: /// - Returns:
@ -141,9 +141,9 @@ class APASCAPI {
handleError("获取内购列表失败: \(error.localizedDescription)") handleError("获取内购列表失败: \(error.localizedDescription)")
return [] return []
} }
} }
/// ///
/// - Parameters: /// - Parameters:
/// - appId: apple id /// - appId: apple id
@ -172,7 +172,7 @@ class APASCAPI {
"type": "inAppPurchases" "type": "inAppPurchases"
] ]
] ]
do { do {
guard let provider = provider else { guard let provider = provider else {
return nil return nil
@ -190,7 +190,7 @@ class APASCAPI {
return nil return nil
} }
/// ///
/// - Parameters: /// - Parameters:
/// - iapId: id /// - iapId: id
@ -209,7 +209,7 @@ class APASCAPI {
"type": "inAppPurchases" "type": "inAppPurchases"
] ]
] ]
do { do {
guard let provider = provider else { guard let provider = provider else {
return nil return nil
@ -227,7 +227,7 @@ class APASCAPI {
return nil return nil
} }
/// ///
/// - Parameters: /// - Parameters:
/// - iapId: id /// - iapId: id
@ -248,8 +248,8 @@ class APASCAPI {
} }
return 400 return 400
} }
/// ///
/// - Parameters: /// - Parameters:
/// - iapId: id /// - iapId: id
@ -276,8 +276,8 @@ class APASCAPI {
return [] return []
} }
} }
/// ///
/// - Parameters: /// - Parameters:
/// - scheduleId: id /// - scheduleId: id
@ -308,8 +308,8 @@ class APASCAPI {
] ]
] ]
} }
/// ///
/// - Parameters: /// - Parameters:
/// - iapId: id /// - iapId: id
@ -371,7 +371,7 @@ class APASCAPI {
// ] // ]
// ] // ]
] ]
do { do {
guard let provider = provider else { guard let provider = provider else {
return nil return nil
@ -388,8 +388,8 @@ class APASCAPI {
} }
return nil return nil
} }
/// ///
/// - Parameters: /// - Parameters:
/// - iapId: id /// - iapId: id
@ -413,8 +413,8 @@ class APASCAPI {
return [] return []
} }
} }
/// ///
/// - Parameters: /// - Parameters:
/// - iapId: id /// - iapId: id
@ -439,7 +439,7 @@ class APASCAPI {
"type": "inAppPurchaseLocalizations" "type": "inAppPurchaseLocalizations"
] ]
] ]
do { do {
guard let provider = provider else { guard let provider = provider else {
return nil return nil
@ -457,7 +457,7 @@ class APASCAPI {
return nil return nil
} }
/// ///
/// - Parameters: /// - Parameters:
/// - iapLocaleId: id /// - iapLocaleId: id
@ -491,7 +491,7 @@ class APASCAPI {
return nil return nil
} }
/// ///
/// - Parameters: /// - Parameters:
/// - iapLocaleId: id /// - iapLocaleId: id
@ -512,8 +512,8 @@ class APASCAPI {
} }
return 400 return 400
} }
/// ///
/// - Parameter iapId: id /// - Parameter iapId: id
/// - Returns: /// - Returns:
@ -530,7 +530,7 @@ class APASCAPI {
return nil return nil
} }
} }
/// ///
/// - Parameters: /// - Parameters:
/// - iapId: id /// - iapId: id
@ -555,7 +555,7 @@ class APASCAPI {
"type": "inAppPurchaseAppStoreReviewScreenshots" "type": "inAppPurchaseAppStoreReviewScreenshots"
] ]
] ]
do { do {
guard let provider = provider else { guard let provider = provider else {
return nil return nil
@ -572,8 +572,8 @@ class APASCAPI {
} }
return nil return nil
} }
/// ///
/// - Parameters: /// - Parameters:
/// - iapShotId: id /// - iapShotId: id
@ -590,7 +590,7 @@ class APASCAPI {
"type": "inAppPurchaseAppStoreReviewScreenshots" "type": "inAppPurchaseAppStoreReviewScreenshots"
] ]
] ]
do { do {
guard let provider = provider else { guard let provider = provider else {
return nil return nil
@ -607,7 +607,7 @@ class APASCAPI {
} }
return nil return nil
} }
/// ///
/// - Parameters: /// - Parameters:
/// - iapShotId: id /// - iapShotId: id
@ -628,8 +628,8 @@ class APASCAPI {
} }
return 400 return 400
} }
/// ///
/// - Parameters: /// - Parameters:
/// - iapId: id /// - iapId: id
@ -662,7 +662,7 @@ class APASCAPI {
] ]
] ]
] ]
do { do {
guard let provider = provider else { guard let provider = provider else {
return nil return nil
@ -679,11 +679,11 @@ class APASCAPI {
} }
return nil return nil
} }
// MARK: - API // MARK: - API
/// ///
/// - Parameters: /// - Parameters:
/// - appId: apple id /// - appId: apple id
@ -707,8 +707,8 @@ class APASCAPI {
return [] return []
} }
} }
/// ///
/// - Parameters: /// - Parameters:
/// - appId: apple id /// - appId: apple id
@ -731,7 +731,7 @@ class APASCAPI {
"type": "subscriptionGroups" "type": "subscriptionGroups"
] ]
] ]
do { do {
guard let provider = provider else { guard let provider = provider else {
return nil return nil
@ -748,8 +748,8 @@ class APASCAPI {
} }
return nil return nil
} }
/// ///
/// - Parameters: /// - Parameters:
/// - iapGroupId: id /// - iapGroupId: id
@ -773,8 +773,8 @@ class APASCAPI {
return [] return []
} }
} }
/// ///
/// - Parameters: /// - Parameters:
/// - iapGroupId: id /// - iapGroupId: id
@ -801,7 +801,7 @@ class APASCAPI {
"type": "subscriptionGroupLocalizations" "type": "subscriptionGroupLocalizations"
] ]
] ]
do { do {
guard let provider = provider else { guard let provider = provider else {
return nil return nil
@ -818,8 +818,8 @@ class APASCAPI {
} }
return nil return nil
} }
/// ///
/// - Parameters: /// - Parameters:
/// - appId: apple id /// - appId: apple id
@ -843,8 +843,8 @@ class APASCAPI {
return [] return []
} }
} }
/// ///
/// - Parameters: /// - Parameters:
/// - iapGroupId: apple id /// - iapGroupId: apple id
@ -873,7 +873,7 @@ class APASCAPI {
"type": "subscriptions" "type": "subscriptions"
] ]
] ]
do { do {
guard let provider = provider else { guard let provider = provider else {
return nil return nil
@ -890,8 +890,8 @@ class APASCAPI {
} }
return nil return nil
} }
func updateSubscription(iapId: String, product: IAPProduct) async -> ASCSubscription? { func updateSubscription(iapId: String, product: IAPProduct) async -> ASCSubscription? {
let body = [ let body = [
"data": [ "data": [
@ -923,8 +923,8 @@ class APASCAPI {
} }
return nil return nil
} }
/// ///
/// - Parameters: /// - Parameters:
/// - iapId: id /// - iapId: id
@ -949,8 +949,8 @@ class APASCAPI {
} }
} }
/// ///
/// - Parameters: /// - Parameters:
/// - iapId: id /// - iapId: id
@ -975,7 +975,7 @@ class APASCAPI {
"type": "subscriptionLocalizations" "type": "subscriptionLocalizations"
] ]
] ]
do { do {
guard let provider = provider else { guard let provider = provider else {
return nil return nil
@ -993,7 +993,7 @@ class APASCAPI {
return nil return nil
} }
/// ///
/// - Parameters: /// - Parameters:
/// - iapLocaleId: id /// - iapLocaleId: id
@ -1027,8 +1027,8 @@ class APASCAPI {
} }
return nil return nil
} }
/// ///
/// - Parameters: /// - Parameters:
/// - iapId: id /// - iapId: id
@ -1055,8 +1055,8 @@ class APASCAPI {
return [] return []
} }
} }
/// ///
/// - Parameters: /// - Parameters:
/// - pointId: id /// - pointId: id
@ -1083,8 +1083,8 @@ class APASCAPI {
return [] return []
} }
} }
/// ///
/// - Parameters: /// - Parameters:
/// - iapId: id /// - iapId: id
@ -1115,7 +1115,7 @@ class APASCAPI {
] ]
] ]
] ]
do { do {
guard let provider = provider else { guard let provider = provider else {
return nil return nil
@ -1132,8 +1132,8 @@ class APASCAPI {
} }
return nil return nil
} }
/// ///
func fetchSubscriptionScreenshot(iapId: String) async -> ASCSubscriptionScreenshot? { func fetchSubscriptionScreenshot(iapId: String) async -> ASCSubscriptionScreenshot? {
let request = APIEndpoint.v1.subscriptions.id(iapId).appStoreReviewScreenshot.get() let request = APIEndpoint.v1.subscriptions.id(iapId).appStoreReviewScreenshot.get()
@ -1143,12 +1143,23 @@ class APASCAPI {
} }
let shot = try await provider.request(request).data let shot = try await provider.request(request).data
return shot return shot
} catch APIProvider.Error.decodingError(let decodeError, let data) {
if let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
json["data"] is NSNull {
return nil
}
let err = APIProvider.Error.decodingError(decodeError, data)
handleError("获取订阅商品的送审截图异常: \(err.localizedDescription)")
return nil
} catch APIProvider.Error.requestFailure(let statusCode, let errorResponse, _) {
handleRequestFailure(statusCode, errorResponse)
return nil
} catch { } catch {
handleError("获取订阅商品的送审截图异常: \(error.localizedDescription)") handleError("获取订阅商品的送审截图异常: \(error.localizedDescription)")
return nil return nil
} }
} }
/// ///
func createSubscriptionScreenshot(iapId: String, fileName: String, fileSize: Int) async -> ASCSubscriptionScreenshot? { func createSubscriptionScreenshot(iapId: String, fileName: String, fileSize: Int) async -> ASCSubscriptionScreenshot? {
let body = [ let body = [
@ -1203,7 +1214,7 @@ class APASCAPI {
"type": "subscriptionAppStoreReviewScreenshots" "type": "subscriptionAppStoreReviewScreenshots"
] ]
] ]
do { do {
guard let provider = provider else { guard let provider = provider else {
return nil return nil
@ -1220,7 +1231,7 @@ class APASCAPI {
} }
return nil return nil
} }
/// ///
/// - Parameters: /// - Parameters:
/// - iapShotId: id /// - iapShotId: id
@ -1241,8 +1252,8 @@ class APASCAPI {
} }
return 400 return 400
} }
/// ///
/// - Parameters: /// - Parameters:
/// - iapId: id /// - iapId: id
@ -1275,7 +1286,7 @@ class APASCAPI {
] ]
] ]
] ]
do { do {
guard let provider = provider else { guard let provider = provider else {
return nil return nil
@ -1297,30 +1308,30 @@ class APASCAPI {
// MARK: - handle log // MARK: - handle log
extension APASCAPI { extension APASCAPI {
func handleRequestFailure(_ statusCode: Int, _ errorResponse: ErrorResponse?) { func handleRequestFailure(_ statusCode: Int, _ errorResponse: ErrorResponse?) {
print("Request failed with statuscode: \(statusCode) and the following errors:") print("Request failed with statuscode: \(statusCode) and the following errors:")
errorResponse?.errors?.forEach({ error in errorResponse?.errors?.forEach({ error in
handleError("Error code: \(error.code), title: \(error.title), detail: \(String(describing: error.detail))") handleError("Error code: \(error.code), title: \(error.title), detail: \(String(describing: error.detail))")
}) })
} }
func handleError(_ msg: String) { func handleError(_ msg: String) {
addMessage(msg) addMessage(msg)
print(msg) print(msg)
} }
func addMessage(_ msg: String) { func addMessage(_ msg: String) {
let dateFormatter : DateFormatter = DateFormatter() let dateFormatter : DateFormatter = DateFormatter()
dateFormatter.dateFormat = "[MM-dd HH:mm:ss] " dateFormatter.dateFormat = "[MM-dd HH:mm:ss] "
let currentDateString = dateFormatter.string(from: Date()) let currentDateString = dateFormatter.string(from: Date())
let log = currentDateString + msg let log = currentDateString + msg
message.append(log) message.append(log)
// //
saveLogs(log: log) saveLogs(log: log)
} }
func saveLogs(log: String, retry: Int = 3) { func saveLogs(log: String, retry: Int = 3) {
do { do {
try log.appendLine(to: logPath.createFilePath) try log.appendLine(to: logPath.createFilePath)