130 lines
6.0 KiB
Swift
130 lines
6.0 KiB
Swift
import Foundation
|
|
|
|
/// 进入业务页面前的核验结果,不将非空未知记录解释为任何已知注销状态。
|
|
enum StoreAccountDeregistrationAccessDecision: Equatable {
|
|
case notChecked
|
|
case checking
|
|
case allowed
|
|
case cooling
|
|
case unresolved
|
|
case failed(String)
|
|
case obsolete
|
|
}
|
|
|
|
/// 进入业务前核验当前身份;冷静期只能查询或由用户明确撤销,不自动重新登录。
|
|
final class StoreAccountDeregistrationAccessViewModel {
|
|
/// 未核验与查询失败都不能创建普通业务根页面。
|
|
private(set) var decision: StoreAccountDeregistrationAccessDecision = .notChecked
|
|
/// 防止同一页面重复发起查询。
|
|
private(set) var isChecking = false
|
|
/// 最近一次通过身份核验的服务端状态,用于冷静期展示和撤销前比对。
|
|
private(set) var status: StoreAccountDeregistrationStatus?
|
|
private var restrictionRevision = 0
|
|
private var requiresSubmissionReconciliation: Bool
|
|
private let submissionStore: (any StoreAccountDeregistrationSubmissionTracking)?
|
|
private var observedCooling = false
|
|
|
|
/// 本机存在待核实提交意图时,即使暂时返回 null 也不直接恢复普通业务。
|
|
init(requiresSubmissionReconciliation: Bool = false,
|
|
submissionStore: (any StoreAccountDeregistrationSubmissionTracking)? = nil) {
|
|
self.submissionStore = submissionStore
|
|
self.requiresSubmissionReconciliation = requiresSubmissionReconciliation
|
|
|| submissionStore?.hasUnresolvedSubmission == true
|
|
}
|
|
|
|
/// 新的限制信号优先于此前已发出的状态查询,防止旧正常响应覆盖新限制。
|
|
func recordRestriction() {
|
|
restrictionRevision += 1
|
|
status = nil
|
|
decision = .unresolved
|
|
}
|
|
|
|
/// 查询前后核对身份;旧身份响应和错误均不能决定新会话是否进入业务页。
|
|
func verify(api: any StoreAccountDeregistrationStatusServing,
|
|
isCurrentIdentity: @escaping @MainActor () -> Bool) async {
|
|
guard !isChecking else { return }
|
|
isChecking = true
|
|
let revision = restrictionRevision
|
|
decision = .checking
|
|
status = nil
|
|
defer { isChecking = false }
|
|
guard await isCurrentIdentity() else { decision = .obsolete; return }
|
|
do {
|
|
let result = try await api.status()
|
|
guard await isCurrentIdentity() else { decision = .obsolete; return }
|
|
guard restrictionRevision == revision else { return }
|
|
apply(result)
|
|
} catch {
|
|
guard await isCurrentIdentity() else { decision = .obsolete; return }
|
|
guard restrictionRevision == revision else { return }
|
|
if case APIError.serverCode(150015, _) = error {
|
|
decision = .unresolved
|
|
} else if case StoreAccountDeregistrationError.sessionChanged = error {
|
|
decision = .obsolete
|
|
} else {
|
|
decision = .failed(error.localizedDescription)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 用户确认后先重查同一申请再撤销,结果丢失时保留限制,禁止自动重发 POST。
|
|
func cancel(api: any StoreAccountDeregistrationServing,
|
|
isCurrentIdentity: @escaping @MainActor () -> Bool) async {
|
|
guard !isChecking, decision == .cooling, let recordID = status?.recordID else { return }
|
|
isChecking = true
|
|
let revision = restrictionRevision
|
|
defer { isChecking = false }
|
|
guard await isCurrentIdentity() else { decision = .obsolete; return }
|
|
do {
|
|
let current = try await api.status()
|
|
guard await isCurrentIdentity() else { decision = .obsolete; return }
|
|
guard restrictionRevision == revision else { return }
|
|
guard current.isCooling, current.recordID == recordID else { applyCancellationCheck(current); return }
|
|
try await api.cancel()
|
|
guard await isCurrentIdentity() else { decision = .obsolete; return }
|
|
guard restrictionRevision == revision else { return }
|
|
let confirmed = try await api.status()
|
|
guard await isCurrentIdentity() else { decision = .obsolete; return }
|
|
guard restrictionRevision == revision else { return }
|
|
applyCancellationCheck(confirmed)
|
|
} catch {
|
|
guard await isCurrentIdentity() else { decision = .obsolete; return }
|
|
guard restrictionRevision == revision else { return }
|
|
status = nil
|
|
decision = .failed("撤销结果尚未确认,请重新查询状态。\n" + error.localizedDescription)
|
|
}
|
|
}
|
|
|
|
private func applyCancellationCheck(_ result: StoreAccountDeregistrationStatus) {
|
|
// 曾查到冷静期后,旧 null/草稿不能证明撤销完成;须重新核实完整状态。
|
|
guard result.isCooling || result.isCancelled else {
|
|
status = result
|
|
decision = .unresolved
|
|
return
|
|
}
|
|
apply(result)
|
|
}
|
|
|
|
private func apply(_ result: StoreAccountDeregistrationStatus) {
|
|
status = result
|
|
if submissionStore?.hasUnresolvedSubmission == true { requiresSubmissionReconciliation = true }
|
|
if result.isCancelled, submissionStore?.clearCancelledSubmission(matching: result) == true {
|
|
requiresSubmissionReconciliation = false
|
|
}
|
|
if result.isCooling {
|
|
observedCooling = true
|
|
decision = .cooling
|
|
} else {
|
|
decision = result.permitsPreparation && !requiresSubmissionReconciliation
|
|
&& (!observedCooling || result.isCancelled) ? .allowed : .unresolved
|
|
}
|
|
}
|
|
|
|
/// 150015 只影响发出请求时的同一门店身份凭证,不能按手机号传播限制。
|
|
static func restrictionApplies(requestToken: String?, session: AppSessionStore) -> Bool {
|
|
guard session.accountType == .storeUser,
|
|
let requestToken, !requestToken.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else { return false }
|
|
return requestToken == session.token
|
|
}
|
|
}
|