220 lines
6.4 KiB
Swift
220 lines
6.4 KiB
Swift
//
|
|
// WithdrawalSettingsViewModel.swift
|
|
// suixinkan
|
|
//
|
|
|
|
import Foundation
|
|
|
|
/// 提现设置 ViewModel,管理银行卡绑定两步表单与提交状态。
|
|
final class WithdrawalSettingsViewModel {
|
|
private(set) var currentStep = 1
|
|
private(set) var holderName = ""
|
|
private(set) var bankNo = ""
|
|
private(set) var bankCardFrontURL = ""
|
|
private(set) var bankCardBackURL = ""
|
|
private(set) var bankName = ""
|
|
private(set) var provinceCode = ""
|
|
private(set) var provinceName = ""
|
|
private(set) var cityCode = ""
|
|
private(set) var cityName = ""
|
|
private(set) var branchName = ""
|
|
private(set) var verificationCode = ""
|
|
private(set) var isAgree = false
|
|
private(set) var banks: [String] = []
|
|
private(set) var areas: [AreasResponse] = []
|
|
private(set) var loading = false
|
|
private(set) var sendingCode = false
|
|
private(set) var submitting = false
|
|
|
|
private(set) var pendingFrontImageData: Data?
|
|
private(set) var pendingBackImageData: Data?
|
|
private var pendingFrontFileName: String?
|
|
private var pendingBackFileName: String?
|
|
|
|
var onStateChange: (() -> Void)?
|
|
|
|
var canGoNextStep: Bool {
|
|
currentStep == 1
|
|
&& !holderName.trimmed.isEmpty
|
|
&& (12 ... 25).contains(bankNo.filter(\.isNumber).count)
|
|
&& !bankCardFrontURL.isEmpty
|
|
&& !bankCardBackURL.isEmpty
|
|
&& isAgree
|
|
}
|
|
|
|
var canSubmit: Bool {
|
|
currentStep == 2
|
|
&& !holderName.trimmed.isEmpty
|
|
&& (12 ... 25).contains(bankNo.filter(\.isNumber).count)
|
|
&& !bankName.trimmed.isEmpty
|
|
&& !provinceCode.isEmpty
|
|
&& !cityCode.isEmpty
|
|
&& !branchName.trimmed.isEmpty
|
|
&& !verificationCode.trimmed.isEmpty
|
|
&& isAgree
|
|
&& !submitting
|
|
}
|
|
|
|
func updateHolderName(_ value: String) {
|
|
holderName = value
|
|
notifyStateChange()
|
|
}
|
|
|
|
func updateBankNo(_ value: String) {
|
|
bankNo = value
|
|
notifyStateChange()
|
|
}
|
|
|
|
func updateBankName(_ value: String) {
|
|
bankName = value
|
|
notifyStateChange()
|
|
}
|
|
|
|
func updateBranchName(_ value: String) {
|
|
branchName = value
|
|
notifyStateChange()
|
|
}
|
|
|
|
func updateVerificationCode(_ value: String) {
|
|
verificationCode = value
|
|
notifyStateChange()
|
|
}
|
|
|
|
func updateAgree(_ value: Bool) {
|
|
isAgree = value
|
|
notifyStateChange()
|
|
}
|
|
|
|
func selectProvince(_ area: AreasResponse) {
|
|
provinceCode = area.code
|
|
provinceName = area.name
|
|
cityCode = ""
|
|
cityName = ""
|
|
notifyStateChange()
|
|
}
|
|
|
|
func selectCity(_ area: AreasResponse) {
|
|
cityCode = area.code
|
|
cityName = area.name
|
|
notifyStateChange()
|
|
}
|
|
|
|
func goNextStep() {
|
|
guard canGoNextStep else { return }
|
|
currentStep = 2
|
|
notifyStateChange()
|
|
}
|
|
|
|
func prepareBankCardImage(data: Data, side: BankCardImageSide) throws {
|
|
let processed = try BankCardImageProcessor.process(data: data, side: side)
|
|
switch side {
|
|
case .front:
|
|
pendingFrontImageData = processed.data
|
|
pendingFrontFileName = processed.fileName
|
|
bankCardFrontURL = "pending"
|
|
case .back:
|
|
pendingBackImageData = processed.data
|
|
pendingBackFileName = processed.fileName
|
|
bankCardBackURL = "pending"
|
|
}
|
|
notifyStateChange()
|
|
}
|
|
|
|
func load(api: ProfileAPI) async throws {
|
|
guard !loading else { return }
|
|
loading = true
|
|
notifyStateChange()
|
|
defer {
|
|
loading = false
|
|
notifyStateChange()
|
|
}
|
|
async let bankList = api.bankList()
|
|
async let areaList = api.areas()
|
|
let result = try await (bankList, areaList)
|
|
banks = result.0.banks
|
|
areas = result.1
|
|
notifyStateChange()
|
|
}
|
|
|
|
func sendCode(api: ProfileAPI) async throws {
|
|
guard !sendingCode else { return }
|
|
sendingCode = true
|
|
notifyStateChange()
|
|
defer {
|
|
sendingCode = false
|
|
notifyStateChange()
|
|
}
|
|
try await api.bankCardSmsVerifyCode()
|
|
}
|
|
|
|
func submit(api: ProfileAPI, uploader: any OSSUploadServing, scenicId: Int) async throws {
|
|
guard canSubmit else {
|
|
throw WithdrawalValidationError.message("请完善银行卡信息")
|
|
}
|
|
submitting = true
|
|
notifyStateChange()
|
|
defer {
|
|
submitting = false
|
|
notifyStateChange()
|
|
}
|
|
try await uploadPendingImages(uploader: uploader, scenicId: scenicId)
|
|
let request = UpdateBankInfoRequest(
|
|
realName: holderName.trimmed,
|
|
cardNumber: bankNo.filter(\.isNumber),
|
|
bankName: bankName.trimmed,
|
|
branchName: branchName.trimmed,
|
|
frontUrl: bankCardFrontURL,
|
|
backUrl: bankCardBackURL,
|
|
provinceCode: provinceCode,
|
|
cityCode: cityCode,
|
|
smsVerifyCode: verificationCode.trimmed
|
|
)
|
|
try await api.updateBankCard(request)
|
|
}
|
|
|
|
var provinceOptions: [AreasResponse] { areas }
|
|
|
|
var cityOptions: [AreasResponse] {
|
|
areas.first { $0.code == provinceCode || $0.name == provinceName }?.children ?? []
|
|
}
|
|
|
|
private func uploadPendingImages(uploader: any OSSUploadServing, scenicId: Int) async throws {
|
|
if let pendingFrontImageData {
|
|
bankCardFrontURL = try await uploader.uploadBankCardImage(
|
|
data: pendingFrontImageData,
|
|
fileName: pendingFrontFileName ?? "bank_card_front.jpg",
|
|
scenicId: scenicId
|
|
) { _ in }
|
|
self.pendingFrontImageData = nil
|
|
}
|
|
if let pendingBackImageData {
|
|
bankCardBackURL = try await uploader.uploadBankCardImage(
|
|
data: pendingBackImageData,
|
|
fileName: pendingBackFileName ?? "bank_card_back.jpg",
|
|
scenicId: scenicId
|
|
) { _ in }
|
|
self.pendingBackImageData = nil
|
|
}
|
|
}
|
|
|
|
private func notifyStateChange() {
|
|
onStateChange?()
|
|
}
|
|
}
|
|
|
|
enum WithdrawalValidationError: LocalizedError, Equatable {
|
|
case message(String)
|
|
|
|
var errorDescription: String? {
|
|
switch self {
|
|
case .message(let message): message
|
|
}
|
|
}
|
|
}
|
|
|
|
private extension String {
|
|
var trimmed: String {
|
|
trimmingCharacters(in: .whitespacesAndNewlines)
|
|
}
|
|
}
|