feat: 增加门店身份注销流程

This commit is contained in:
2026-08-31 09:43:14 +08:00
parent 396597a160
commit e529bb5942
35 changed files with 6062 additions and 45 deletions
@@ -0,0 +1,105 @@
import Foundation
/// 门店身份注销依赖;网络服务在主线程协调,ViewModel 自身不绑定 MainActor。
@MainActor
protocol StoreAccountDeregistrationStatusServing {
/// 只读查询当前注销记录,供进入普通业务前核验。
func status() async throws -> StoreAccountDeregistrationStatus
}
/// 完整注销服务,在状态查询之外提供条件、确认和申请操作。
@MainActor
protocol StoreAccountDeregistrationServing: StoreAccountDeregistrationStatusServing {
/// 获取资产与全部注销条件。
func eligibility() async throws -> StoreAccountDeregistrationEligibility
/// 分别确认现金余额。
func waiveWallet() async throws
/// 分别确认积分。
func waivePoints() async throws
/// 向当前身份的绑定手机号发送短信。
func sendSMS() async throws
/// 提交用户输入的验证码与注销原因。
func apply(smsCode: String, reason: String) async throws
/// 服务端判定是否允许撤销,不按本机截止时间判断。
func cancel() async throws
}
/// 旧门店身份注销接口请求层;仅使用提供的 account-deregister 路径和请求字段。
@MainActor
final class StoreAccountDeregistrationAPI: StoreAccountDeregistrationServing {
private let client: APIClient
private let identity: StoreAccountDeregistrationIdentity
private let isCurrentIdentity: @MainActor () -> Bool
private let basePath = "/api/yf-handset-app/account-deregister"
/// 注入网络客户端、冻结的门店身份和当前会话校验,便于隔离真实与测试请求。
init(
client: APIClient,
identity: StoreAccountDeregistrationIdentity,
isCurrentIdentity: @escaping @MainActor () -> Bool
) {
self.client = client
self.identity = identity
self.isCurrentIdentity = isCurrentIdentity
}
/// 查询当前门店身份的资产、风险期和全部阻断项。
func eligibility() async throws -> StoreAccountDeregistrationEligibility {
let value: StoreAccountDeregistrationEligibility = try await send(APIRequest(method: .get, path: basePath + "/eligibility"))
guard String(value.storeUserID) == identity.userID else {
throw StoreAccountDeregistrationError.sessionChanged
}
return value
}
/// 调用旧现金确认接口;与积分接口的顺序协调由ViewModel负责。
func waiveWallet() async throws {
let _: EmptyPayload = try await send(APIRequest(
method: .post,
path: basePath + "/waivers/wallet",
body: StoreAccountDeregistrationWaiverRequest()
))
}
/// 调用旧积分确认接口;与现金接口的顺序协调由ViewModel负责。
func waivePoints() async throws {
let _: EmptyPayload = try await send(APIRequest(
method: .post,
path: basePath + "/waivers/points",
body: StoreAccountDeregistrationWaiverRequest()
))
}
/// 向当前门店身份绑定手机号发送短信;收件人由后端从 Token 确定。
func sendSMS() async throws {
let _: EmptyPayload = try await send(APIRequest(method: .post, path: basePath + "/send-sms"))
}
/// 使用用户输入的验证码和原因提交申请;成功后需查询服务端状态。
func apply(smsCode: String, reason: String) async throws {
let _: EmptyPayload = try await send(APIRequest(
method: .post,
path: basePath + "/apply",
body: StoreAccountDeregistrationApplyRequest(smsCode: smsCode, reason: reason)
))
}
/// 查询当前身份的草稿、冷静期、撤销、阻断或完成状态,不触发重新登录。
func status() async throws -> StoreAccountDeregistrationStatus {
try await send(APIRequest(method: .get, path: basePath + "/status"))
}
/// 正式完成前由用户主动撤销;是否可撤销始终由服务端判定。
func cancel() async throws {
let _: EmptyPayload = try await send(APIRequest(method: .post, path: basePath + "/cancel"))
}
private func send<Response: Decodable>(_ request: APIRequest<Response>) async throws -> Response {
guard isCurrentIdentity() else { throw StoreAccountDeregistrationError.sessionChanged }
var sensitiveRequest = request
sensitiveRequest.logsPayload = false
let response = try await client.send(sensitiveRequest, tokenOverride: identity.token)
guard isCurrentIdentity() else { throw StoreAccountDeregistrationError.sessionChanged }
return response
}
}