103 lines
4.2 KiB
Swift
103 lines
4.2 KiB
Swift
import Foundation
|
|
|
|
/// 旧注销接口的门店身份上下文;冻结身份与凭证,防止切换账号后误操作另一身份。
|
|
struct StoreAccountDeregistrationIdentity: Equatable, Sendable {
|
|
/// 当前门店用户身份 ID,对应 ss_store_user.id,不是门店实体 ID。
|
|
let userID: String
|
|
/// 进入流程时的门店身份 Token,仅用于接口鉴权。
|
|
let token: String
|
|
/// 用于确认页展示当前正在注销的门店身份。
|
|
let displayName: String
|
|
|
|
/// 从当前业务会话构造上下文;景区身份、未知身份和未登录状态均不可发起注销。
|
|
init(session: AppSessionStore) throws {
|
|
guard session.accountType == .storeUser else {
|
|
throw StoreAccountDeregistrationError.unsupportedIdentity
|
|
}
|
|
let id = session.userId.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
let token = session.token.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
guard let numericID = Int(id), numericID > 0, !token.isEmpty else {
|
|
throw StoreAccountDeregistrationError.invalidSession
|
|
}
|
|
userID = id
|
|
self.token = token
|
|
let name = session.accountDisplayName.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
displayName = name.isEmpty ? "当前门店身份" : name
|
|
}
|
|
|
|
/// 校验操作前后仍为同一个身份和同一份登录凭证。
|
|
func matches(session: AppSessionStore) -> Bool {
|
|
session.accountType == .storeUser
|
|
&& session.userId.trimmingCharacters(in: .whitespacesAndNewlines) == userID
|
|
&& session.token.trimmingCharacters(in: .whitespacesAndNewlines) == token
|
|
}
|
|
}
|
|
|
|
/// 门店身份注销的本地安全校验错误,不替代后端业务错误码。
|
|
enum StoreAccountDeregistrationError: LocalizedError, Equatable {
|
|
case unsupportedIdentity
|
|
case invalidSession
|
|
case sessionChanged
|
|
|
|
/// 可直接展示给用户的错误说明。
|
|
var errorDescription: String? {
|
|
switch self {
|
|
case .unsupportedIdentity:
|
|
"当前身份暂不支持注销"
|
|
case .invalidSession:
|
|
"当前门店身份信息不完整,请重新登录"
|
|
case .sessionChanged:
|
|
"登录状态或当前身份已变化,请重新进入注销页面"
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 旧接口的余额或积分放弃确认请求;两种资产必须分别调用对应接口。
|
|
struct StoreAccountDeregistrationWaiverRequest: Encodable, Sendable {
|
|
/// 用户明确接受后才创建该请求,固定提交 true。
|
|
let accepted = true
|
|
}
|
|
|
|
/// 旧接口提交注销申请的请求体,不附加新协议中的主账号 ID 或核验参数。
|
|
struct StoreAccountDeregistrationApplyRequest: Encodable, Sendable {
|
|
/// 用户实际收到的短信验证码,不使用本地固定验证码。
|
|
let smsCode: String
|
|
/// 用户确认的注销原因。
|
|
let reason: String
|
|
|
|
/// 对齐旧文档的请求字段。
|
|
enum CodingKeys: String, CodingKey {
|
|
case smsCode = "sms_code"
|
|
case reason
|
|
}
|
|
}
|
|
|
|
/// 未定义完整响应字段的旧接口原始 JSON;保留信息,待实际契约确认后映射为业务模型。
|
|
/// 此类型不推断状态、余额或确认结果,也不应直接用于决定是否允许注销。
|
|
indirect enum StoreAccountDeregistrationJSON: Decodable, Equatable, Sendable {
|
|
case object([String: StoreAccountDeregistrationJSON])
|
|
case array([StoreAccountDeregistrationJSON])
|
|
case string(String)
|
|
case number(Decimal)
|
|
case bool(Bool)
|
|
case null
|
|
|
|
/// 无损保留 JSON 结构,金额数值使用 Decimal,避免浮点误差。
|
|
init(from decoder: Decoder) throws {
|
|
let container = try decoder.singleValueContainer()
|
|
if container.decodeNil() {
|
|
self = .null
|
|
} else if let value = try? container.decode(Bool.self) {
|
|
self = .bool(value)
|
|
} else if let value = try? container.decode(String.self) {
|
|
self = .string(value)
|
|
} else if let value = try? container.decode(Decimal.self) {
|
|
self = .number(value)
|
|
} else if let value = try? container.decode([String: Self].self) {
|
|
self = .object(value)
|
|
} else {
|
|
self = .array(try container.decode([Self].self))
|
|
}
|
|
}
|
|
}
|