feat: 增加门店身份注销流程
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user