This commit is contained in:
zeng 2026-05-09 10:22:38 +08:00
parent 2095581969
commit 9dd9e9ebde
3 changed files with 134 additions and 2 deletions

View File

@ -13,6 +13,7 @@ class APUploadIAPListVC: NSViewController {
@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!
@IBOutlet weak var submitSubscriptionReviewBtn: NSButton!
public var currentApp: App? { public var currentApp: App? {
didSet { didSet {
@ -151,11 +152,17 @@ extension APUploadIAPListVC {
// 3 // 3
let oldIAPs = await ascAPI.fetchInAppPurchasesList(appId: appId) let oldIAPs = await ascAPI.fetchInAppPurchasesList(appId: appId)
let submitSubscriptionReview = self.submitSubscriptionReviewBtn.state == .on
// 4 // 4
for product in iaps { for product in iaps {
// //
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,
submitForReview: submitSubscriptionReview)
} else { } else {
await createInAppPurchase( await createInAppPurchase(
appId: appId, product: product, oldIAPs: oldIAPs, ascAPI: ascAPI) appId: appId, product: product, oldIAPs: oldIAPs, ascAPI: ascAPI)
@ -480,7 +487,12 @@ extension APUploadIAPListVC {
// MARK: - // MARK: -
/// ///
func createRenewSubscription(appId: String, product: IAPProduct, ascAPI: APASCAPI) async { func createRenewSubscription(
appId: String,
product: IAPProduct,
ascAPI: APASCAPI,
submitForReview: Bool
) async {
let groupName = product.groupName let groupName = product.groupName
var currentSubGroup: ASCSubscriptionGroup? var currentSubGroup: ASCSubscriptionGroup?
// 1 // 1
@ -580,6 +592,10 @@ extension APUploadIAPListVC {
// Apple / PATCH // Apple / PATCH
await refreshSubscriptionMetadata(iapId: iap.id, product: product, ascAPI: ascAPI) await refreshSubscriptionMetadata(iapId: iap.id, product: product, ascAPI: ascAPI)
if submitForReview {
await submitSubscriptionForReview(iapId: iap.id, product: product, ascAPI: ascAPI)
}
} else { } else {
// 1. // 1.
guard let iapGroupId = currentSubGroup?.id, guard let iapGroupId = currentSubGroup?.id,
@ -611,11 +627,57 @@ extension APUploadIAPListVC {
// Apple / PATCH // Apple / PATCH
await refreshSubscriptionMetadata(iapId: iap.id, product: product, ascAPI: ascAPI) await refreshSubscriptionMetadata(iapId: iap.id, product: product, ascAPI: ascAPI)
if submitForReview {
await submitSubscriptionForReview(iapId: iap.id, product: product, ascAPI: ascAPI)
}
} }
ascAPI.addMessage("订阅商品:\(product.productId)\(product.name) ,上传完成!\n") ascAPI.addMessage("订阅商品:\(product.productId)\(product.name) ,上传完成!\n")
} }
///
func submitSubscriptionForReview(iapId: String, product: IAPProduct, ascAPI: APASCAPI) async {
ascAPI.addMessage("开始提交订阅商品审核:\(product.productId)")
let maxRetryCount = 12
for retryIndex in 0..<maxRetryCount {
if let subscription = await ascAPI.fetchSubscription(iapId: iapId),
let state = subscription.attributes?.state {
ascAPI.addMessage("订阅商品:\(product.productId) 当前状态:\(state.rawValue)")
if state == .waitingForReview || state == .inReview {
ascAPI.addMessage("订阅商品:\(product.productId) 已处于审核流程,无需重复提交。✅ ")
return
}
if state == .readyToSubmit {
break
}
} else {
ascAPI.addMessage("订阅商品:\(product.productId) 当前状态获取失败,继续尝试提交审核")
break
}
let waitSeconds: UInt64 = 10
let remainingCount = maxRetryCount - retryIndex - 1
if remainingCount > 0 {
ascAPI.addMessage("订阅商品:\(product.productId) 尚未进入准备提交状态,\(waitSeconds) 秒后重试,剩余 \(remainingCount)")
try? await Task.sleep(nanoseconds: waitSeconds * 1_000_000_000)
}
}
if let submission = await ascAPI.submitSubscriptionForReview(iapId: iapId) {
ascAPI.addMessage("订阅商品:\(product.productId) 提交审核成功提交ID\(submission.id)")
if let subscription = await ascAPI.fetchSubscription(iapId: iapId),
let state = subscription.attributes?.state {
ascAPI.addMessage("订阅商品:\(product.productId) 提交后状态:\(state.rawValue)")
}
} else {
ascAPI.addMessage("订阅商品:\(product.productId) ,提交审核失败!请检查商品是否为准备提交状态,或是否需要随 App 版本提交。❌ ")
}
}
/// ///
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 {

View File

@ -454,11 +454,22 @@ DQ
<constraint firstAttribute="height" constant="16" id="UTk-E1-F8x"/> <constraint firstAttribute="height" constant="16" id="UTk-E1-F8x"/>
</constraints> </constraints>
</button> </button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="SMg-H8-CbS">
<rect key="frame" x="18" y="7" width="154" height="18"/>
<buttonCell key="cell" type="check" title="上传后提交订阅审核" bezelStyle="regularSquare" imagePosition="left" inset="2" id="G3M-3N-oSq">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<constraints>
<constraint firstAttribute="height" constant="16" id="rUz-sG-vbg"/>
</constraints>
</button>
</subviews> </subviews>
<constraints> <constraints>
<constraint firstAttribute="bottom" secondItem="gwi-GP-uQf" secondAttribute="bottom" constant="8" id="446-fY-M9B"/> <constraint firstAttribute="bottom" secondItem="gwi-GP-uQf" secondAttribute="bottom" constant="8" id="446-fY-M9B"/>
<constraint firstAttribute="bottom" secondItem="Wiy-jc-2Pw" secondAttribute="bottom" constant="20" symbolic="YES" id="7js-1K-u7U"/> <constraint firstAttribute="bottom" secondItem="Wiy-jc-2Pw" secondAttribute="bottom" constant="20" symbolic="YES" id="7js-1K-u7U"/>
<constraint firstItem="O5k-L7-xuw" firstAttribute="top" secondItem="mWR-IS-Cxn" secondAttribute="bottom" constant="10" id="8dK-sn-ZXM"/> <constraint firstItem="O5k-L7-xuw" firstAttribute="top" secondItem="mWR-IS-Cxn" secondAttribute="bottom" constant="10" id="8dK-sn-ZXM"/>
<constraint firstItem="SMg-H8-CbS" firstAttribute="top" secondItem="O5k-L7-xuw" secondAttribute="bottom" constant="7" id="7vd-Dp-m2h"/>
<constraint firstItem="yl7-CE-urn" firstAttribute="centerY" secondItem="6rW-KE-ixB" secondAttribute="centerY" id="ITd-YX-Trm"/> <constraint firstItem="yl7-CE-urn" firstAttribute="centerY" secondItem="6rW-KE-ixB" secondAttribute="centerY" id="ITd-YX-Trm"/>
<constraint firstAttribute="trailing" secondItem="sv7-52-2Cw" secondAttribute="trailing" constant="15" id="QUP-CD-c2a"/> <constraint firstAttribute="trailing" secondItem="sv7-52-2Cw" secondAttribute="trailing" constant="15" id="QUP-CD-c2a"/>
<constraint firstItem="Wiy-jc-2Pw" firstAttribute="top" secondItem="sv7-52-2Cw" secondAttribute="bottom" constant="12" id="Rp0-lh-D4G"/> <constraint firstItem="Wiy-jc-2Pw" firstAttribute="top" secondItem="sv7-52-2Cw" secondAttribute="bottom" constant="12" id="Rp0-lh-D4G"/>
@ -466,6 +477,7 @@ DQ
<constraint firstItem="yl7-CE-urn" firstAttribute="centerX" secondItem="6rW-KE-ixB" secondAttribute="centerX" id="efU-0I-QhV"/> <constraint firstItem="yl7-CE-urn" firstAttribute="centerX" secondItem="6rW-KE-ixB" secondAttribute="centerX" id="efU-0I-QhV"/>
<constraint firstItem="mWR-IS-Cxn" firstAttribute="leading" secondItem="6rW-KE-ixB" secondAttribute="leading" constant="20" symbolic="YES" id="hjT-ey-2ZV"/> <constraint firstItem="mWR-IS-Cxn" firstAttribute="leading" secondItem="6rW-KE-ixB" secondAttribute="leading" constant="20" symbolic="YES" id="hjT-ey-2ZV"/>
<constraint firstItem="O5k-L7-xuw" firstAttribute="leading" secondItem="6rW-KE-ixB" secondAttribute="leading" constant="20" symbolic="YES" id="htV-cS-Gxk"/> <constraint firstItem="O5k-L7-xuw" firstAttribute="leading" secondItem="6rW-KE-ixB" secondAttribute="leading" constant="20" symbolic="YES" id="htV-cS-Gxk"/>
<constraint firstItem="SMg-H8-CbS" firstAttribute="leading" secondItem="6rW-KE-ixB" secondAttribute="leading" constant="20" symbolic="YES" id="W7O-7K-uBI"/>
<constraint firstItem="gwi-GP-uQf" firstAttribute="centerX" secondItem="6rW-KE-ixB" secondAttribute="centerX" id="o1O-9Y-VOc"/> <constraint firstItem="gwi-GP-uQf" firstAttribute="centerX" secondItem="6rW-KE-ixB" secondAttribute="centerX" id="o1O-9Y-VOc"/>
<constraint firstItem="mWR-IS-Cxn" firstAttribute="top" secondItem="6rW-KE-ixB" secondAttribute="top" constant="25" id="yx3-wU-dQT"/> <constraint firstItem="mWR-IS-Cxn" firstAttribute="top" secondItem="6rW-KE-ixB" secondAttribute="top" constant="25" id="yx3-wU-dQT"/>
</constraints> </constraints>
@ -490,6 +502,7 @@ DQ
<outlet property="enterBtn" destination="yl7-CE-urn" id="fxo-dC-Eo4"/> <outlet property="enterBtn" destination="yl7-CE-urn" id="fxo-dC-Eo4"/>
<outlet property="preserveCurrentPriceBtn" destination="mWR-IS-Cxn" id="kC5-n4-ddM"/> <outlet property="preserveCurrentPriceBtn" destination="mWR-IS-Cxn" id="kC5-n4-ddM"/>
<outlet property="showApiRateLimitLogsBtn" destination="O5k-L7-xuw" id="NkA-Kq-jRr"/> <outlet property="showApiRateLimitLogsBtn" destination="O5k-L7-xuw" id="NkA-Kq-jRr"/>
<outlet property="submitSubscriptionReviewBtn" destination="SMg-H8-CbS" id="IUP-9g-WUu"/>
<outlet property="tableView" destination="FfT-VW-kGY" id="eo0-Hf-anq"/> <outlet property="tableView" destination="FfT-VW-kGY" id="eo0-Hf-anq"/>
</connections> </connections>
</viewController> </viewController>

View File

@ -31,6 +31,7 @@ typealias ASCSubscriptionScreenshot = AppStoreConnect_Swift_SDK.SubscriptionAppS
typealias ASCSubscriptionGroup = AppStoreConnect_Swift_SDK.SubscriptionGroup typealias ASCSubscriptionGroup = AppStoreConnect_Swift_SDK.SubscriptionGroup
typealias ASCSubscriptionGroupLocale = AppStoreConnect_Swift_SDK.SubscriptionGroupLocalization typealias ASCSubscriptionGroupLocale = AppStoreConnect_Swift_SDK.SubscriptionGroupLocalization
typealias ASCSubscriptionAvailability = AppStoreConnect_Swift_SDK.SubscriptionAvailability typealias ASCSubscriptionAvailability = AppStoreConnect_Swift_SDK.SubscriptionAvailability
typealias ASCSubscriptionSubmission = AppStoreConnect_Swift_SDK.SubscriptionSubmission
class APASCAPI { class APASCAPI {
@ -844,6 +845,27 @@ class APASCAPI {
} }
} }
///
/// - Parameter iapId: id
/// - Returns:
func fetchSubscription(iapId: String) async -> ASCSubscription? {
let request = APIEndpoint.v1.subscriptions.id(iapId).get(parameters: .init(
fieldsSubscriptions: [.name, .productID, .state]
))
do {
guard let provider = provider else {
return nil
}
return try await provider.request(request).data
} catch APIProvider.Error.requestFailure(let statusCode, let errorResponse, _) {
handleRequestFailure(statusCode, errorResponse)
} catch {
handleError("获取订阅商品详情失败: \(error.localizedDescription)")
}
return nil
}
/// ///
/// - Parameters: /// - Parameters:
@ -1303,6 +1325,41 @@ class APASCAPI {
} }
return nil return nil
} }
/// App Review
/// - Parameter iapId: id
/// - Returns:
func submitSubscriptionForReview(iapId: String) async -> ASCSubscriptionSubmission? {
let body = [
"data": [
"type": "subscriptionSubmissions",
"relationships": [
"subscription": [
"data": [
"id": iapId,
"type": "subscriptions"
]
]
]
]
]
do {
guard let provider = provider else {
return nil
}
let json = try JSONSerialization.data(withJSONObject: body, options: .prettyPrinted)
let model = try JSONDecoder().decode(SubscriptionSubmissionCreateRequest.self, from: json)
let request = APIEndpoint.v1.subscriptionSubmissions.post(model)
let data = try await provider.request(request).data
return data
} catch APIProvider.Error.requestFailure(let statusCode, let errorResponse, _) {
handleRequestFailure(statusCode, errorResponse)
} catch {
handleError("提交订阅商品审核失败: \(error.localizedDescription)")
}
return nil
}
} }